From 648893bd6ff5f4c403733d03258dbf3e91f2394b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 25 May 2026 19:48:39 -0500 Subject: feat(save): detect dirty structured fields against synced baselines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The save scan now flags priority, state, assignee, and labels as dirty when their live value differs from the synced baseline, so structured fields ride the same reconcile-at-save path as title and description. Each check is id/scalar-only and runs with no network: the priority cookie's number against LINEAR-PRIORITY, the explicit state id against LINEAR-STATE-ID-SYNCED (the picker arm — keyword cycling lands later), the assignee id against its baseline, and the label id set against its baseline (order-insensitive, since Linear label order isn't meaningful). A missing baseline on a legacy file reads as not-dirty; the saver's missing-property guard covers that edge. This also makes the priority cookie faithful. The renderer mapped both None and Medium to [#C], so a None issue couldn't be told apart from a Medium one and would have read dirty the moment its baseline said 0. None now renders with no cookie, 1:1 with the four cookie levels — matching what the priority setter already did. --- tests/test-pearl-save.el | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'tests/test-pearl-save.el') diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el index a8ae6af..36358ad 100644 --- a/tests/test-pearl-save.el +++ b/tests/test-pearl-save.el @@ -112,6 +112,68 @@ (re-search-forward "Original description") (should-not (plist-get (pearl--issue-dirty-fields) :comment-candidates)))) +;;; --issue-dirty-fields: structured fields (save-model v2) + +(defun test-pearl-save--structured-issue () + "A normalized issue with state id, assignee, and labels (for structured dirty)." + '(:id "u" :identifier "ENG-1" :title "t" :priority 2 + :state (:id "s1" :name "Todo") + :assignee (:id "a1" :name "Craig") + :labels ((:id "l1" :name "bug") (:id "l2" :name "backend")) + :description "body")) + +(ert-deftest test-pearl-dirty-fields-structured-clean () + "A freshly rendered issue has no dirty structured fields." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (let ((d (pearl--issue-dirty-fields))) + (should-not (plist-get d :priority)) + (should-not (plist-get d :state)) + (should-not (plist-get d :assignee)) + (should-not (plist-get d :labels))))) + +(ert-deftest test-pearl-dirty-fields-priority-cookie-change () + "Changing the priority cookie marks priority dirty vs LINEAR-PRIORITY." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (org-back-to-heading t) + (let ((org-priority-highest ?A) (org-priority-lowest ?D)) + (org-priority ?C)) ; was [#B] (2); now [#C] (3) + (should (plist-get (pearl--issue-dirty-fields) :priority)))) + +(ert-deftest test-pearl-dirty-fields-priority-none-not-dirty () + "A None-priority issue renders no cookie and is not falsely dirty." + (test-pearl-save--in-rendered + '(:id "u" :identifier "ENG-2" :title "t" :priority 0 + :state (:id "s1" :name "Todo") :description "body") + (should-not (plist-get (pearl--issue-dirty-fields) :priority)))) + +(ert-deftest test-pearl-dirty-fields-state-picker-change () + "An explicit state id different from the synced baseline marks state dirty." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (org-back-to-heading t) + (org-entry-put nil "LINEAR-STATE-ID" "s2") + (should (plist-get (pearl--issue-dirty-fields) :state)))) + +(ert-deftest test-pearl-dirty-fields-assignee-change () + "An assignee id different from the synced baseline marks assignee dirty." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (org-back-to-heading t) + (org-entry-put nil "LINEAR-ASSIGNEE-ID" "a2") + (should (plist-get (pearl--issue-dirty-fields) :assignee)))) + +(ert-deftest test-pearl-dirty-fields-labels-change () + "A label id set different from the synced baseline marks labels dirty." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (org-back-to-heading t) + (org-entry-put nil "LINEAR-LABEL-IDS" "l2 l3") + (should (plist-get (pearl--issue-dirty-fields) :labels)))) + +(ert-deftest test-pearl-dirty-fields-labels-reorder-not-dirty () + "Reordering the same label ids is not dirty (order-insensitive set compare)." + (test-pearl-save--in-rendered (test-pearl-save--structured-issue) + (org-back-to-heading t) + (org-entry-put nil "LINEAR-LABEL-IDS" "l2 l1") + (should-not (plist-get (pearl--issue-dirty-fields) :labels)))) + ;;; --classify-comment-candidates (Phase B, by viewer id) (ert-deftest test-pearl-classify-comment-candidates () -- cgit v1.2.3