fix(composer): durable publish state for thread idempotency (closes #17) #34
No reviewers
Labels
No labels
auth
bug
composer
correctness
database
durability
enhancement
feeds
interactions
notifications
performance
reliability
safety
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
starsetbyte/peregrine!34
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/composer-idempotent-publish"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Issue #17 identified two failure modes in thread publication:
publish()call generated fresh rkeys, so if segment N'screateRecordtimed out but actually succeeded server-side, retrying re-posted it with a new rkey — creating a duplicate thread root. The pre-generated rkeys weren't persisted or reused.PartialPublishException, the ViewModel deleted the draft and closed the composer, so unposted segments were gone for good.Fix — durable publish outbox keyed to the draft
drafts.publishStateJson(new column, Room v8→v9 additiveMIGRATION_8_9): a JSON array ofSegmentPublishState(rkey, published, uri, cid), one per segment.publish()now takes adraftId, generates rkeys once, persists them before the loop, and updates each segment's state as it succeeds. On retry (same draftId with persisted state) it:post()flushes the draft (so the row exists) then publishes. On partial failure it keeps the draft and surfacespartialProgress("Published N of M — Retry will resume — no duplicate posts") with Retry / Discard remaining buttons instead of closing. Non-partial failures clear the publish state so the next manual post starts clean.initdetects a persisted partial-publish state on composer open and shows the resume UI immediately.Test plan
ComposerPublishStateTest(6 new tests):clearPublishStatenulls the columnhasPartialPublishlogic (mixed/all-published/no-state)assembleDebugbuilds, Room schema v9 regenerates💘 Generated with Crush
Thread publication reused fresh rkeys on every publish() call, so an ambiguous timeout on segment N that actually succeeded server-side would create a duplicate thread on retry. On partial failure the draft was deleted and the composer closed, losing unposted segments. Add a durable publish outbox keyed to the draft: rkeys are generated once, persisted to a new drafts.publishStateJson column (Room v8→v9 additive migration) before the loop, and reused on retry. Each segment records its published uri/cid as it succeeds. A resume skips already-published segments and reconstructs the thread root/parent chain from the persisted state, so re-POSTing an in-flight rkey upserts instead of duplicating. The composer now surfaces "Published N of M — Retry" on partial failure instead of closing, and resume also works across process death (the ViewModel detects a persisted partial state on open). Non-partial failures clear the publish state so the next manual post starts clean. 💘 Generated with Crush