feat: merge checkbox state on pull instead of overwriting it
A tick was lost in both directions: pull wrote the server body as-is, push sent the local body as-is, last writer won. Tick it in the web UI and the first `push.py --update` dropped it; tick it locally and the first pull dropped it. The usual answer is drift tracking and a three-way merge, which this repo rejected on purpose. It is not needed. A tick is monotone — an item only travels `[ ]` -> `[x]` — so unioning the two sides is a set union, not conflict resolution. One rule for one line type replaces the whole mechanism, and the store stays "not a mirror". `map.merge_checkbox_state` is pure and does the work; `from_api` takes the local body as an optional argument; `pull.py` hands it the copy already on disk. Checkbox parsing is imported from `skills/issue` (`checkboxes` / `set_checkbox`), never redefined here — the domain layer is untouched. The same item text more than once is read as a set: one ticked local item ticks every server line with that text. Pairing duplicates up by order is the alternative, and it can still drop a tick — which is the bug being fixed. The price is documented, not hidden: unticking is not monotone, so a box unticked in the web UI comes back on the next pull. Untick locally, then push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,11 @@ Other flags:
|
||||
--repo owner/repo default: auto-detect from the CWD git remote
|
||||
|
||||
Pulling overwrites the local body: it is a fetch, not a merge. Local edits you
|
||||
have not pushed are lost. Draw the graph afterwards with the domain's own
|
||||
have not pushed are lost — with exactly one exception, checkbox state. A `[x]`
|
||||
on either side wins for any item whose text matches, because a tick is monotone
|
||||
and unioning the two sides is not conflict resolution (gmap.merge_checkbox_state
|
||||
has the rule and its price). `--cached` skips an issue before any of that: it is
|
||||
not read and not merged. Draw the graph afterwards with the domain's own
|
||||
issue_tree.py — it needs no network.
|
||||
|
||||
Login: the operator's pin from .claude/settings.local.json (see /tea:auth).
|
||||
@@ -187,13 +191,18 @@ def main():
|
||||
store_ids.add(id)
|
||||
number_of_id[number] = id
|
||||
if args.cached and stored:
|
||||
skipped.append(id) # untouched, and not one request spent on it
|
||||
skipped.append(id) # untouched, unread, and not one request spent
|
||||
else:
|
||||
extra = _gitea.native_deps(login, base, number) if args.deps else []
|
||||
# The copy already on disk, as it was when this run started. It
|
||||
# contributes its ticked checkboxes and nothing else; None when
|
||||
# the store has never seen this issue.
|
||||
prev = issues.get(id)
|
||||
iss, unresolved = gmap.from_api(payload, id, repo,
|
||||
id_for_number=number_of_id,
|
||||
extra_numbers=extra,
|
||||
synced=_gitea.now_iso())
|
||||
synced=_gitea.now_iso(),
|
||||
local_body=prev.body if prev else None)
|
||||
issue.save(root, iss)
|
||||
sync_comments(login, base, root, id, number, payload.get("comments") or 0)
|
||||
remote_map[gmap.remote_key(repo, number)] = id
|
||||
|
||||
Reference in New Issue
Block a user