diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-25 21:06:42 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-25 21:06:42 -0500 |
| commit | f9841539b3adb192d0c15470fa9c31d6cbbdf447 (patch) | |
| tree | 490433f58ccb3106e72b6685a7b3d99b3699788a /pearl.el | |
| parent | 684068a81103e9a9dace2f5cc6fd49edc1f01ab1 (diff) | |
| download | pearl-f9841539b3adb192d0c15470fa9c31d6cbbdf447.tar.gz pearl-f9841539b3adb192d0c15470fa9c31d6cbbdf447.zip | |
feat(save): reconcile structured fields in save-issue and save-all
The four structured savers now run from the save engine. pearl--save-field-thunks queues a priority / state / assignee / labels saver for each dirty structured field alongside title, description, and comments, so save-issue and save-all reconcile a changed cookie, keyword-picker id, assignee, or label set at save time like any other edit. save-all's confirm prompt and the dirty-scan gate both count the structured fields, and a new pearl--issue-has-dirty-fields-p replaces the title/description/comment check that was duplicated in save-issue and the file-wide scan.
This is the commit that turns the engine on. The old immediate-push setters still exist until the next commit retires them, so for this one commit both paths are live — the setter cutover and the org-sync removal follow.
Diffstat (limited to 'pearl.el')
| -rw-r--r-- | pearl.el | 67 |
1 files changed, 50 insertions, 17 deletions
@@ -3863,13 +3863,32 @@ push sends `labelIds'." (org-entry-put marker "LINEAR-LABEL-IDS-SYNCED" remote-str))))) (pearl--run-atomic-field-save spec callback)))) +(defun pearl--issue-has-dirty-fields-p (dirty) + "Non-nil if the DIRTY plist (from `pearl--issue-dirty-fields') has any change." + (or (plist-get dirty :title) + (plist-get dirty :description) + (plist-get dirty :priority) + (plist-get dirty :state) + (plist-get dirty :assignee) + (plist-get dirty :labels) + (plist-get dirty :comment-candidates))) + (defun pearl--save-field-thunks (marker dirty viewer-id) "Build the ordered save thunks for the DIRTY fields of the issue at MARKER. -Title first, then description, then each changed comment candidate (VIEWER-ID -gates ownership inside `pearl--save-comment-field')." +Title, then the structured fields (priority / state / assignee / labels), then +description, then each changed comment candidate (VIEWER-ID gates ownership +inside `pearl--save-comment-field')." (let (thunks) (when (plist-get dirty :title) (push (lambda (done) (pearl--save-title-field marker done)) thunks)) + (when (plist-get dirty :priority) + (push (lambda (done) (pearl--save-priority-field marker done)) thunks)) + (when (plist-get dirty :state) + (push (lambda (done) (pearl--save-state-field marker done)) thunks)) + (when (plist-get dirty :assignee) + (push (lambda (done) (pearl--save-assignee-field marker done)) thunks)) + (when (plist-get dirty :labels) + (push (lambda (done) (pearl--save-labels-field marker done)) thunks)) (when (plist-get dirty :description) (push (lambda (done) (pearl--save-description-field marker done)) thunks)) (dolist (c (plist-get dirty :comment-candidates)) @@ -3893,19 +3912,18 @@ was pushed. The summary is labelled with the issue identifier." (defun pearl-save-issue () "Save every changed field of the Linear issue at point in one pass. Runs from anywhere inside an issue subtree. A local dirty scan (no network) -picks which of the title, description, and your own comments changed; the -viewer is resolved once, and only if comments are dirty; then each dirty field -is saved sequentially through its conflict gate and a single summary is -reported. A clean issue does no network at all." +picks which of the title, structured fields (priority / state / assignee / +labels), description, and your own comments changed; the viewer is resolved +once, and only if comments are dirty; then each dirty field is saved +sequentially through its conflict gate and a single summary is reported. A +clean issue does no network at all." (interactive) (save-excursion (pearl--goto-issue-heading-or-error) (let* ((marker (point-marker)) (buffer (current-buffer)) (dirty (pearl--issue-dirty-fields))) - (if (not (or (plist-get dirty :title) - (plist-get dirty :description) - (plist-get dirty :comment-candidates))) + (if (not (pearl--issue-has-dirty-fields-p dirty)) (message "Nothing to save for %s" (or (org-entry-get nil "LINEAR-IDENTIFIER") (org-entry-get nil "LINEAR-ID"))) @@ -3925,9 +3943,7 @@ wait for the next save (each field's content is still re-read at push)." (dolist (cell (pearl--issue-subtree-markers)) (let* ((marker (cdr cell)) (dirty (org-with-point-at marker (pearl--issue-dirty-fields)))) - (when (or (plist-get dirty :title) - (plist-get dirty :description) - (plist-get dirty :comment-candidates)) + (when (pearl--issue-has-dirty-fields-p dirty) (push (cons marker dirty) result)))) (nreverse result))) @@ -3936,15 +3952,19 @@ wait for the next save (each field's content is still re-read at push)." When VIEWER-FAILED is non-nil (the viewer lookup ran and failed), every comment candidate counts as `:viewer-unavailable' rather than being classified as own / read-only -- a nil VIEWER-ID then means \"couldn't tell\", not \"none are -yours\". Returns a plist (:issues :titles :descriptions :own-comments -:read-only-comments :viewer-unavailable)." - (let ((issues 0) (titles 0) (descriptions 0) (own 0) (read-only 0) - (viewer-unavailable 0)) +yours\". Returns a plist (:issues :titles :descriptions :priorities :states +:assignees :labels :own-comments :read-only-comments :viewer-unavailable)." + (let ((issues 0) (titles 0) (descriptions 0) (priorities 0) (states 0) + (assignees 0) (labels 0) (own 0) (read-only 0) (viewer-unavailable 0)) (dolist (cell scan) (let ((dirty (cdr cell))) (setq issues (1+ issues)) (when (plist-get dirty :title) (setq titles (1+ titles))) (when (plist-get dirty :description) (setq descriptions (1+ descriptions))) + (when (plist-get dirty :priority) (setq priorities (1+ priorities))) + (when (plist-get dirty :state) (setq states (1+ states))) + (when (plist-get dirty :assignee) (setq assignees (1+ assignees))) + (when (plist-get dirty :labels) (setq labels (1+ labels))) (let ((cands (plist-get dirty :comment-candidates))) (if viewer-failed (setq viewer-unavailable (+ viewer-unavailable (length cands))) @@ -3952,6 +3972,7 @@ yours\". Returns a plist (:issues :titles :descriptions :own-comments (setq own (+ own (length (plist-get split :own)))) (setq read-only (+ read-only (length (plist-get split :read-only))))))))) (list :issues issues :titles titles :descriptions descriptions + :priorities priorities :states states :assignees assignees :labels labels :own-comments own :read-only-comments read-only :viewer-unavailable viewer-unavailable))) @@ -3959,16 +3980,28 @@ yours\". Returns a plist (:issues :titles :descriptions :own-comments "Return the one-time confirmation prompt string for the dirty COUNTS plist." (let* ((titles (plist-get counts :titles)) (descriptions (plist-get counts :descriptions)) + (priorities (or (plist-get counts :priorities) 0)) + (states (or (plist-get counts :states) 0)) + (assignees (or (plist-get counts :assignees) 0)) + (labels (or (plist-get counts :labels) 0)) (own (plist-get counts :own-comments)) (read-only (plist-get counts :read-only-comments)) (viewer-unavailable (or (plist-get counts :viewer-unavailable) 0)) - (fields (+ titles descriptions own)) + (fields (+ titles descriptions priorities states assignees labels own)) (issues (plist-get counts :issues)) parts skips) (when (> titles 0) (push (format "%d title%s" titles (if (= titles 1) "" "s")) parts)) (when (> descriptions 0) (push (format "%d description%s" descriptions (if (= descriptions 1) "" "s")) parts)) + (when (> priorities 0) + (push (format "%d priorit%s" priorities (if (= priorities 1) "y" "ies")) parts)) + (when (> states 0) + (push (format "%d state%s" states (if (= states 1) "" "s")) parts)) + (when (> assignees 0) + (push (format "%d assignee%s" assignees (if (= assignees 1) "" "s")) parts)) + (when (> labels 0) + (push (format "%d label%s" labels (if (= labels 1) "" "s")) parts)) (when (> own 0) (push (format "%d comment%s" own (if (= own 1) "" "s")) parts)) (when (> read-only 0) |
