branch: externals/gnosis
commit f20be60895b0968d60747ffecc9be824eb1546ac
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    [Feature] Review topic with configurable link depth.
---
 gnosis-dashboard.el | 49 ++++++++++++++++++++++++++----------------
 gnosis.el           | 62 ++++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 85 insertions(+), 26 deletions(-)

diff --git a/gnosis-dashboard.el b/gnosis-dashboard.el
index 3defcbb2c6..c17c329a2f 100644
--- a/gnosis-dashboard.el
+++ b/gnosis-dashboard.el
@@ -306,23 +306,19 @@ If IDS is not provided, use current themata being 
displayed."
                            :join review-log :on (= themata:id review-log:id)
                            :where (in themata:id ,(vconcat thema-ids))])))
     (cl-loop for sublist in entries
-             collect
-            (let ((vec (vconcat
-                        (cl-loop for item in (cdr sublist)
-                                 if (listp item)
-                                 collect (mapconcat (lambda (x) (format "%s" 
x)) item ",")
-                                 else
-                                 collect
-                                 (let ((formatted (replace-regexp-in-string 
"\n" " " (format "%s" item))))
-                                   ;; Strip org-link markup, keeping only 
descriptions
-                                   (replace-regexp-in-string
-                                    "\\[\\[id:[^]]+\\]\\[\\(.*?\\)\\]\\]"
-                                    "\\1"
-                                    formatted))))))
-              ;; Format suspend column (last) as Yes/No
-              (aset vec (1- (length vec))
-                    (if (equal (aref vec (1- (length vec))) "1") "Yes" "No"))
-              (list (car sublist) vec)))))
+            for fields = (cl-loop for item in (cdr sublist)
+                                  if (listp item)
+                                  collect (mapconcat (lambda (x) (format "%s" 
x)) item ",")
+                                  else collect
+                                  (let ((formatted (replace-regexp-in-string 
"\n" " " (format "%s" item))))
+                                    (replace-regexp-in-string
+                                     "\\[\\[id:[^]]+\\]\\[\\(.*?\\)\\]\\]"
+                                     "\\1" formatted)))
+            ;; Last field is suspend (0/1) — format as Yes/No
+            collect (list (car sublist)
+                          (vconcat (append (butlast fields)
+                                           (list (if (equal (car (last 
fields)) "1")
+                                                     "Yes" "No"))))))))
 
 (defun gnosis-dashboard--update-entries (ids)
   "Re-fetch and update tabulated-list entries for IDS."
@@ -1199,6 +1195,18 @@ Moves cursor to the beginning of the buffer after 
sorting."
     ("t" "By tag" gnosis-dashboard-nodes-filter-by-tag)
     ("q" "Cancel" transient-quit-one)]])
 
+(defun gnosis-dashboard-nodes-review ()
+  "Review themata for node at point."
+  (interactive)
+  (gnosis-review-topic (tabulated-list-get-id)))
+
+(defun gnosis-dashboard-nodes-review-with-depth ()
+  "Review themata for node at point, prompting for link depths."
+  (interactive)
+  (gnosis-review-topic (tabulated-list-get-id)
+                      (read-number "Forward link depth: " 1)
+                      (read-number "Backlink depth: " 0)))
+
 (transient-define-prefix gnosis-dashboard-nodes-mode-menu ()
   "Transient menu for nodes dashboard mode."
   [["Navigate"
@@ -1214,7 +1222,10 @@ Moves cursor to the beginning of the buffer after 
sorting."
     ("b" "Show backlinks" gnosis-dashboard-nodes-show-backlinks)
     ("t" "Show themata links" gnosis-dashboard-nodes-show-themata-links)
     ("i" "Show isolated" gnosis-dashboard-nodes-show-isolated)
-    ("d" "Show due" gnosis-dashboard-nodes-show-due)]])
+    ("d" "Show due" gnosis-dashboard-nodes-show-due)]
+   ["Review"
+    ("r" "Review topic" gnosis-dashboard-nodes-review)
+    ("R" "Review with depth" gnosis-dashboard-nodes-review-with-depth)]])
 
 (defvar-keymap gnosis-dashboard-nodes-mode-map
   :doc "Keymap for nodes dashboard."
@@ -1226,6 +1237,8 @@ Moves cursor to the beginning of the buffer after 
sorting."
   "t" #'gnosis-dashboard-nodes-show-themata-links
   "i" #'gnosis-dashboard-nodes-show-isolated
   "d" #'gnosis-dashboard-nodes-show-due
+  "r" #'gnosis-dashboard-nodes-review
+  "R" #'gnosis-dashboard-nodes-review-with-depth
   "s" #'gnosis-dashboard-nodes-sort-menu
   "SPC" #'gnosis-dashboard-nodes-search-menu
   "l" #'gnosis-dashboard-nodes-filter-menu
diff --git a/gnosis.el b/gnosis.el
index bdf5972290..23b7cf3120 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -1500,17 +1500,63 @@ FN: Review function, defaults to 
`gnosis-review-session'"
         (topic-id (caar (org-gnosis-select 'id 'nodes `(= title 
,topic-title)))))
     topic-id))
 
+(defun gnosis-collect-nodes-at-depth (node-id &optional fwd-depth back-depth)
+  "Collect node IDs reachable from NODE-ID within depth limits.
+FWD-DEPTH is max hops for forward links (default 0).
+BACK-DEPTH is max hops for backlinks (default 0).
+Returns a deduplicated list including NODE-ID itself."
+  (let ((fwd-depth (or fwd-depth 0))
+       (back-depth (or back-depth 0))
+       (max-depth (max fwd-depth back-depth))
+       (visited (make-hash-table :test 'equal))
+       (queue (list node-id)))
+    (puthash node-id t visited)
+    (dotimes (level max-depth)
+      (when queue
+       (let* ((qvec (vconcat queue))
+              (neighbors (append
+                          (when (< level fwd-depth)
+                            (org-gnosis-select 'dest 'links
+                                               `(in source ,qvec) t))
+                          (when (< level back-depth)
+                            (org-gnosis-select 'source 'links
+                                               `(in dest ,qvec) t))))
+              (next-queue nil))
+         (dolist (neighbor neighbors)
+           (unless (gethash neighbor visited)
+             (puthash neighbor t visited)
+             (push neighbor next-queue)))
+         (setq queue next-queue))))
+    (hash-table-keys visited)))
+
 ;;;###autoload
-(defun gnosis-review-topic (&optional node-id)
-  "Review gnosis for topic with NODE-ID."
-  (interactive)
+(defun gnosis-review-topic (&optional node-id fwd-depth back-depth)
+  "Review themata linked to topic NODE-ID.
+FWD-DEPTH and BACK-DEPTH control forward/backlink traversal depth.
+With prefix arg, prompt for depths."
+  (interactive
+   (list nil
+        (when current-prefix-arg (read-number "Forward link depth: " 1))
+        (when current-prefix-arg (read-number "Backlink depth: " 0))))
   (let* ((node-id (or node-id (gnosis-review--select-topic)))
-        (node-title (car (org-gnosis-select 'title 'nodes `(= id ,node-id) t)))
-        (gnosis-questions (gnosis-select 'source 'links `(= dest ,node-id) t)))
+        (fwd-depth (or fwd-depth 0))
+        (back-depth (or back-depth 0))
+        (node-title (car (org-gnosis-select 'title 'nodes
+                                            `(= id ,node-id) t)))
+        (node-ids (if (or (> fwd-depth 0) (> back-depth 0))
+                      (gnosis-collect-nodes-at-depth
+                       node-id fwd-depth back-depth)
+                    (list node-id)))
+        (gnosis-questions (gnosis-select 'source 'links
+                                         `(in dest ,(vconcat node-ids)) t)))
     (if (and gnosis-questions
-            (y-or-n-p (format "Review %s thema(s) for '%s'?"
-                              (length gnosis-questions)
-                              node-title)))
+            (y-or-n-p
+             (format "Review %s thema(s) for '%s'%s?"
+                     (length gnosis-questions) node-title
+                     (if (> (length node-ids) 1)
+                         (format " (%d nodes, fwd:%d back:%d)"
+                                 (length node-ids) fwd-depth back-depth)
+                       ""))))
        (gnosis-review-session gnosis-questions)
       (message "No thema found for %s (id:%s)" node-title node-id))))
 

Reply via email to