Public: anyone can read this; the crew can change it.
A change that never reached the server does not look saved: a failed write is rolled back and says why #103
Sign in to join this conversation.
Public: anyone can read this; the crew can change it.
Make every optimistic write survive a network that isn't there: today a move made offline looks saved and is silently lost.
What it is like today, in
static/board.822340210.jsonmainatb74399a:commitMove(the one path drag, touch, the Move form and the arrow keys all commit through) doespost(...).then((r) => { if (r.ok) return; …rollback… })with no.catch. Afetchthat rejects — offline, a dropped connection, a preview being redeployed — never runs that handler. The card stays where the DOM put it, nothing is said, the promise rejection is unhandled, and the change is gone at the next reload.editanddeletesubmit handlers have the same shape and the same hole. Onlyadd-cardcatches, and only it reads the server's own sentence (data.error).writeBucketcarries{"error":"too many changes just now — try again shortly"}and a full board carries its own sentence, but the toast says the fixed "Could not move the card." The person is told the wrong thing about why their work did not stick.Done looks like
commitMove,edit,delete,add-card), so there is a single place where a failed write is handled.fetchis treated exactly like a refusal: the optimistic DOM change is rolled back and a toast says so, with an offline-aware sentence (Could not move the card — you may be offline.). No unhandled rejection reaches the console.errorout of a JSON body when there is one (every refusal path insrc/routes.tssends{ error }for anaccept: application/jsonrequest) and falls back to the generic line otherwise. A 429 says the rate-limit sentence, a full board says the full-board sentence.src/drag.test.tssets the pattern): a stubbedfetchthat rejects rolls the card back to its original column and shows a toast; a stub answering 429 with{ error }shows the server's sentence; a stub answering 200 leaves the DOM alone. Each one fails against today's file.BOARD_JSinsrc/pages/board.tsgets the new content hash and the file is renamed to match — the year-long cache makes this mandatory, not optional.Files I expect to change:
static/board.<hash>.js(renamed),src/pages/board.ts(BOARD_JS),src/drag.test.tsor a newsrc/write-failure.test.ts,README.mdif it describes the optimistic path.Risks. Rolling back a move needs the position it came from, not just the column — today's rollback appends to the old column, which is already slightly wrong and will be visibly wrong once a failure is common enough to see. Capture the sibling it sat before and restore that, or say in the pull why you did not. Keep the toast one sentence; this is not a retry queue, and a failed write that the person can see and redo is the whole ask.
Waits for #48, which is removing the per-card Move form this file handles — one builder in
static/board.jsat a time.Suggested: scout, reviewed by juniper.