Offline write-queue: coalesce + replay without duplicating (#33) #58

Merged
christian merged 2 commits from feat/offline-write-queue into main 2026-07-09 21:57:14 +00:00
Owner

While gitea is unreachable, propose-approved writes queue instead of being lost; on reconnect they replay in order. The hard requirement (#33): no duplication.

The mechanism is coalescing by axis. Every write targets one field of one issue (estimate / priority / assignee / milestone), so issue:kind is the axis identity. Queuing a second write to the same axis supersedes the first, so a burst of edits replays as one final write — and replay is idempotent (the apply path no-ops a change already reflected server-side).

  • enqueueWrite (coalesce), pendingWrites, coalesceKey, affectedIssues, and replayQueue(queue, apply) — drains through the same guarded write path and returns still-failing writes so they stay queued. Never throws.

Acceptance — a burst of offline edits + reconnect lands the final state with a single apply per axis (not one per edit); still-failing writes stay queued. +4 core tests; typecheck green.

Follow-up: persist the queue in main + trigger replay on the reconnect signal (the offline banner + disabled composer already exist) — the coalesce/replay core is the tested heart here.

🤖 Generated with Claude Code

While gitea is unreachable, propose-approved writes **queue instead of being lost**; on reconnect they replay in order. The hard requirement (#33): **no duplication**. The mechanism is **coalescing by axis**. Every write targets one field of one issue (estimate / priority / assignee / milestone), so `issue:kind` is the axis identity. Queuing a second write to the same axis **supersedes** the first, so a burst of edits replays as one final write — and replay is **idempotent** (the apply path no-ops a change already reflected server-side). - `enqueueWrite` (coalesce), `pendingWrites`, `coalesceKey`, `affectedIssues`, and `replayQueue(queue, apply)` — drains through the same guarded write path and returns still-failing writes so they stay queued. Never throws. **Acceptance ✅** — a burst of offline edits + reconnect lands the final state with a *single* apply per axis (not one per edit); still-failing writes stay queued. +4 core tests; typecheck green. **Follow-up:** persist the queue in main + trigger replay on the reconnect signal (the offline banner + disabled composer already exist) — the coalesce/replay core is the tested heart here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
christian added 1 commit 2026-07-09 19:40:23 +00:00
While gitea is unreachable, propose-approved writes queue instead of being lost;
on reconnect they replay in order. The hard requirement is no duplication.

The mechanism is coalescing by axis. Every write targets one field of one issue
(estimate / priority / assignee / milestone), so `issue:kind` is the axis identity.
Queuing a second write to the same axis supersedes the first (moved to the tail),
so a burst of edits replays as one final write — and replay is idempotent (the
apply path no-ops a change already reflected server-side).

- `enqueueWrite` (coalesce), `pendingWrites`, `coalesceKey`, `affectedIssues`
  (what a post-replay reconcile re-reads), and `replayQueue(queue, apply)` which
  drains through the same guarded write path and returns the writes that still
  failed so they stay queued. Never throws — a failure is data.

Acceptance met: a burst of offline edits + reconnect lands the final state with a
single apply per axis (not one per edit); still-failing writes stay queued. +4
core tests; typecheck green.

Follow-up: persist the queue in main + trigger replay on the reconnect signal
(the offline banner + disabled composer already exist) — the coalesce/replay
core is the tested heart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
christian added 1 commit 2026-07-09 21:56:32 +00:00
- index.ts: keep both cache (#3) and queue (#33) exports
- agent.test.ts: #28 landed the standup impl but left agent.test.ts asserting
  standup is notImplemented (its real test moved to query-project.test.ts);
  retarget the unbuilt-view assertion to milestone/runway, which are still stubs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
christian merged commit b07f07c14d into main 2026-07-09 21:57:14 +00:00
Sign in to join this conversation.
No description provided.