aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-save.el
Commit message (Collapse)AuthorAgeFilesLines
* feat(save): reconcile structured fields in save-issue and save-allCraig Jennings2026-05-251-0/+37
| | | | | | 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.
* feat(save): add the priority, state, and labels saversCraig Jennings2026-05-251-0/+64
| | | | | | | | | | These are three more savers on the atomic engine, each the same spec shape the assignee saver proved, with its own field handling. Priority reads the cookie's number, diffs it against LINEAR-PRIORITY, and pushes priority. State is the picker arm — the explicit LINEAR-STATE-ID against LINEAR-STATE-ID-SYNCED, pushing stateId, with use-remote rewriting the keyword and name from the fetched node (the keyword-cycle arm stays gated on the derivation task). Labels canonicalize the id set to a sorted space-joined string so the diff is order-insensitive, and push labelIds. Canonicalizing labels to a string also keeps the missing-baseline check honest: an issue with no labels has a present-but-empty baseline, which a nil-vs-empty-list comparison would misread as a legacy file. A shared set of pearl--node-* helpers pulls each field out of the raw issue node. I also fixed pearl--get-linear-priority-name, which mapped None to "Medium" — it now reads "None", so the conflict prompt names the priority honestly. It's still not wired into save-issue — the next commit does that.
* feat(save): add the atomic field-save engine and the assignee saverCraig Jennings2026-05-251-0/+109
| | | | | | | | pearl--run-atomic-field-save is the structured-field sibling of the text engine pearl--run-field-save. It runs the atomic three-way over a spec's live and baseline values. A missing baseline skips with missing-property (a legacy file — refresh first). Live equal to baseline is unchanged. Otherwise it fetches the remote and compares: remote already equal to live converges and advances the baseline, remote unmoved from baseline is a clean push, and a genuine divergence runs the atomic conflict gate. It reuses the existing save-outcome contract, so structured fields report the same status/reason shape as title and description. pearl--save-assignee-field is the first saver on it — the cleanest relation field. Its live id is LINEAR-ASSIGNEE-ID diffed against LINEAR-ASSIGNEE-ID-SYNCED, the remote comes from the issue node, the push sends assigneeId, and use-remote rewrites the display name from the fetched node. Priority, state, and labels follow the same template in the next commit. It's not wired into save-issue yet — that's the next commit. I tested it in isolation: the engine's three-way and conflict branches over a synthetic spec, plus the assignee saver end to end with a stubbed fetch and update.
* feat(save): detect dirty structured fields against synced baselinesCraig Jennings2026-05-251-0/+62
| | | | | | 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.
* refactor: rename pearl--issue-body-at-point to pearl--entry-body-at-pointCraig Jennings2026-05-251-2/+2
| | | | The helper returns the body of the Org entry at point, before any child heading. It started out reading issue descriptions, but comment editing and deletion reuse it for comment bodies too, so "issue" in the name was misleading. I renamed it across its callers and the sync/refresh/save/comment tests, and reworded the docstring to say "entry".
* feat: add the unified ticket save model with save-issue and save-allCraig Jennings2026-05-251-1/+323
| | | | | | | | | | | | Editing a ticket meant remembering which per-field command to run: pearl-sync-current-issue for the description, pearl-sync-current-issue-title for the heading, pearl-edit-current-comment for a comment. Three fields, three commands, and no single way to edit several fields (or several tickets) and push them at once. I added a layered save engine and two commands over it. pearl--run-field-save does the fetch, the three-way conflict gate, and the push for one field, emitting a single structured outcome instead of messaging from its callback. The three interactive sync commands are now thin wrappers over the per-field savers, so they keep working and gain the outcome. pearl-save-issue diffs the ticket at point and pushes only what changed. pearl-save-all scans the whole file, confirms once naming the field counts, and saves every diff in a sequential pass that continues past a per-ticket conflict. A queue runner keeps at most one conflict-resolution buffer live at a time. I also fixed a correctness bug: a description push now advances both LINEAR-DESC-SHA256 (the markdown the remote gate hashes against) and LINEAR-DESC-ORG-SHA256 (the rendered-Org baseline the local scan uses). Advancing only the markdown hash left the Org hash stale, so the next local scan would flag a just-saved ticket as dirty again. The conflict resolver gained an optional outcome callback so the engine can report cancel, use-local, use-remote, and the deferred smerge commit or abort as distinct outcomes. I removed the now-dead pearl--commit-sync-decision. The keybinding scheme and the transient menu retarget are a separate follow-up. Spec: docs/ticket-save-model-spec.org.
* feat: add the local dirty scanners for the ticket save modelCraig Jennings2026-05-241-0/+123
First layer of the unified save model (docs/ticket-save-model-spec.org). pearl--issue-dirty-fields returns which of title, description, and comment candidates changed in the issue subtree at point, with no network call. Description dirtiness reuses pearl--subtree-dirty-p, which hashes the rendered Org against LINEAR-DESC-ORG-SHA256, so a description whose markdown is lossy under org->md isn't falsely flagged. Comment candidates hash org->md of each body against LINEAR-COMMENT-SHA256, matching how pearl-edit-current-comment computes its no-op check. Ownership is a separate phase: pearl--classify-comment-candidates splits candidates into own versus read-only once the viewer id is known, since the local scan can't tell who authored a comment without it. The save-issue and save-all commands that consume these scanners land in the next layers.