aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-03 12:36:13 -0500
committerCraig Jennings <c@cjennings.net>2026-06-03 12:36:13 -0500
commita6a08fd6a418a5d404550478bde2883c440377a7 (patch)
tree11568d2152d65680dbe53739999d9dea50a7720c
parent459c257560b00a969cc4e8a15fe73377d6476683 (diff)
downloadpearl-a6a08fd6a418a5d404550478bde2883c440377a7.tar.gz
pearl-a6a08fd6a418a5d404550478bde2883c440377a7.zip
fix(render): nest comments under their issue in a grouped view
In a grouped view, each issue's Comments subtree rendered at the issue's own level instead of one deeper, so the Comments heading sat as a sibling of its ticket rather than a child. The grouping feature gave pearl--format-issue-as-org-entry a level parameter (issues render at level 3 under a group heading) but left the comment subtree hardcoded: pearl--format-comments emitted "*** Comments" and pearl--format-comment "**** ". That nests right when the issue is at level 2 (flat) and breaks when it's at level 3 (grouped). I thread the issue's level through, so the Comments heading is level+1 and each comment body level+2: a flat issue stays 2/3/4, a grouped one becomes 3/4/5.
-rw-r--r--pearl.el32
-rw-r--r--tests/test-pearl-list-comments.el26
2 files changed, 47 insertions, 11 deletions
diff --git a/pearl.el b/pearl.el
index e9f1e8b..d2423ae 100644
--- a/pearl.el
+++ b/pearl.el
@@ -2715,8 +2715,10 @@ fetch. Both are documented in the conversion matrix."
(t (push (pearl--org-line-to-md line) out))))
(string-join (nreverse out) "\n"))))
-(defun pearl--format-comment (comment)
- "Format a normalized COMMENT plist as a level-4 Org entry.
+(defun pearl--format-comment (comment &optional level)
+ "Format a normalized COMMENT plist as an Org entry.
+LEVEL is the heading depth (number of stars), defaulting to 4 -- a grouped view
+renders one level deeper so the comment stays nested under its issue.
The heading carries the author and timestamp; a property drawer carries the
comment id, the author's user id (empty for bot/external comments, which are
not editable), and a sha256 of the last-fetched body for the sync conflict
@@ -2725,8 +2727,9 @@ A null author renders as `(unknown)'."
(let ((author (or (plist-get comment :author) "(unknown)"))
(created (or (plist-get comment :created-at) ""))
(raw-body (or (plist-get comment :body) ""))
- (body (pearl--md-to-org (or (plist-get comment :body) ""))))
- (concat (format "**** %s — %s\n" author created)
+ (body (pearl--md-to-org (or (plist-get comment :body) "")))
+ (stars (make-string (or level 4) ?*)))
+ (concat (format "%s %s — %s\n" stars author created)
":PROPERTIES:\n"
(format ":LINEAR-COMMENT-ID: %s\n" (or (plist-get comment :id) ""))
(format ":LINEAR-COMMENT-AUTHOR-ID: %s\n" (or (plist-get comment :author-id) ""))
@@ -2756,23 +2759,29 @@ newest, like an email thread, and a new comment appends at the bottom."
(const :tag "Oldest first (chronological)" oldest-first))
:group 'pearl)
-(defun pearl--format-comments (comments &optional count-info)
+(defun pearl--format-comments (comments &optional count-info level)
"Format COMMENTS (a list of normalized comment plists) as a Comments subtree.
-Comments render under a level-3 `Comments' heading, ordered per
-`pearl-comment-sort-order'.
+LEVEL is the parent issue's heading depth (default 2): the `Comments' heading
+renders at LEVEL+1 and each comment body at LEVEL+2, so the subtree stays nested
+under its issue whether the issue is flat (level 2) or grouped (level 3).
+Ordered per `pearl-comment-sort-order'.
Returns the empty string when COMMENTS is nil, so an issue with no comments
renders no subtree. COUNT-INFO, when non-nil, appends a ` shown/total'
count to the heading (the bulk list passes the issue's `:comment-count')."
(if (null comments)
""
- (let* ((newest-first (eq pearl-comment-sort-order 'newest-first))
+ (let* ((issue-level (or level 2))
+ (heading-stars (make-string (1+ issue-level) ?*))
+ (comment-level (+ 2 issue-level))
+ (newest-first (eq pearl-comment-sort-order 'newest-first))
(sorted (sort (copy-sequence comments)
(lambda (a b)
(let ((ca (or (plist-get a :created-at) ""))
(cb (or (plist-get b :created-at) "")))
(if newest-first (string> ca cb) (string< ca cb)))))))
- (concat "*** Comments" (pearl--comment-count-marker count-info) "\n"
- (mapconcat #'pearl--format-comment sorted "")))))
+ (concat heading-stars " Comments" (pearl--comment-count-marker count-info) "\n"
+ (mapconcat (lambda (c) (pearl--format-comment c comment-level))
+ sorted "")))))
(defun pearl--format-issue-as-org-entry (issue &optional level)
"Format a normalized ISSUE plist as an Org entry.
@@ -2839,7 +2848,8 @@ identifier prefix -- so an unedited heading is a no-op on title sync)."
":END:\n"
(if (string-empty-p body-org) "" (concat body-org "\n"))
(pearl--format-comments (plist-get issue :comments)
- (plist-get issue :comment-count)))))
+ (plist-get issue :comment-count)
+ (or level 2)))))
;;; Description Sync-Back
diff --git a/tests/test-pearl-list-comments.el b/tests/test-pearl-list-comments.el
index 751e220..71d3e73 100644
--- a/tests/test-pearl-list-comments.el
+++ b/tests/test-pearl-list-comments.el
@@ -112,6 +112,32 @@
(let ((out (pearl--format-comments (test-pearl--comments 2))))
(should (string-match-p "^\\*\\*\\* Comments$" out))))
+(ert-deftest test-pearl-format-comments-nests-under-issue-level ()
+ "The Comments subtree nests one level under the issue: a flat issue (level 2,
+the default) gets Comments at level 3 and bodies at level 4; a grouped issue at
+level 3 gets Comments at level 4 and bodies at level 5. Regression guard for
+the grouping bug where comments rendered as siblings of their issue."
+ (let ((comments (test-pearl--comments 1)))
+ ;; flat (default level 2)
+ (should (string-match-p "^\\*\\*\\* Comments$" (pearl--format-comments comments nil 2)))
+ (should (string-match-p "^\\*\\*\\*\\* " (pearl--format-comments comments nil 2)))
+ ;; grouped (issue at level 3)
+ (let ((out (pearl--format-comments comments nil 3)))
+ (should (string-match-p "^\\*\\*\\*\\* Comments$" out))
+ (should (string-match-p "^\\*\\*\\*\\*\\* " out))
+ (should-not (string-match-p "^\\*\\*\\* Comments$" out)))))
+
+(ert-deftest test-pearl-format-issue-grouped-nests-comments-under-issue ()
+ "A level-3 issue with comments renders its Comments subtree at level 4 (a
+child), not level 3 (a sibling) -- the grouped-view regression."
+ (let* ((issue (list :id "u" :identifier "ENG-1" :title "Fix it"
+ :priority 2 :state '(:name "Todo")
+ :comments (test-pearl--comments 1)))
+ (out (pearl--format-issue-as-org-entry issue 3)))
+ (should (string-match-p "^\\*\\*\\* TODO " out)) ; issue at level 3
+ (should (string-match-p "^\\*\\*\\*\\* Comments$" out)) ; Comments nested at 4
+ (should-not (string-match-p "^\\*\\*\\* Comments$" out))))
+
;;; the marked heading is still found by the append locator
(ert-deftest test-pearl-append-finds-marked-comments-heading ()