Canvas cards need bounded recovery after HTTP 429 #1072

Open
opened 2026-10-04 22:24:56 +00:00 by kayg · 2 comments
Owner

Canvas card resolution does not recover after a rate-limited batch

Merge-round #867 ran the real production Canvas card flow. Rapid theme captures reopened the Canvas twelve times, then the flow changed a Note title, completed a Task, dropped a file, and exercised Show as link / Undo. /api/v1/canvas/cards/resolve returned four HTTP 429 responses. The updated card did not become visible within 30 seconds. No authorization bypass or data loss was found.

Evidence: artifacts/merge-round-7c2/canvas-cards-977-current.log at merge-round head 5dff015c9. The test retains the assertion Canvas live card: Card target updated. It does not increase the server quota. The capture fixture now changes the real Appearance preference in the mounted Canvas instead of adding twelve artificial cold route loads.

Product finding: apps/web/src/lib/canvas/cards.ts, flush, maps HTTP 429 to ERROR. startRevalidation skips Notes and Files while the change stream is healthy. A rate-limited visible card has no bounded scheduled retry after the quota window ends unless another item event or explicit Retry occurs. A User can hit this during rapid navigation and edits.

Required follow-up: add a bounded retry for transient rate limits, cancel it when the viewer or mounted card changes, and retain authorization rechecks. Do not poll healthy idle Notes. Add a regression that gets 429, keeps the stream healthy, then resolves after the retry window; check disposal and viewer changes. Keep the existing server quota.

Canvas card resolution does not recover after a rate-limited batch Merge-round #867 ran the real production Canvas card flow. Rapid theme captures reopened the Canvas twelve times, then the flow changed a Note title, completed a Task, dropped a file, and exercised Show as link / Undo. `/api/v1/canvas/cards/resolve` returned four HTTP 429 responses. The updated card did not become visible within 30 seconds. No authorization bypass or data loss was found. Evidence: `artifacts/merge-round-7c2/canvas-cards-977-current.log` at merge-round head 5dff015c9. The test retains the assertion `Canvas live card: Card target updated`. It does not increase the server quota. The capture fixture now changes the real Appearance preference in the mounted Canvas instead of adding twelve artificial cold route loads. Product finding: `apps/web/src/lib/canvas/cards.ts`, `flush`, maps HTTP 429 to ERROR. `startRevalidation` skips Notes and Files while the change stream is healthy. A rate-limited visible card has no bounded scheduled retry after the quota window ends unless another item event or explicit Retry occurs. A User can hit this during rapid navigation and edits. Required follow-up: add a bounded retry for transient rate limits, cancel it when the viewer or mounted card changes, and retain authorization rechecks. Do not poll healthy idle Notes. Add a regression that gets 429, keeps the stream healthy, then resolves after the retry window; check disposal and viewer changes. Keep the existing server quota.
Author
Owner

Starting #1072 repair on branch job/7cfix-small at base 357c856e3d. I will add bounded 429 recovery for mounted Canvas cards, honour Retry-After, preserve rechecks, and test cancellation and viewer changes.

Starting #1072 repair on branch job/7cfix-small at base 357c856e3d2a26f8853781b15b51ad16fc561ced. I will add bounded 429 recovery for mounted Canvas cards, honour Retry-After, preserve rechecks, and test cancellation and viewer changes.
Author
Owner

Finished #1072 on branch job/7cfix-small, based on 357c856e3.

The Canvas card store now shows a retrying status after HTTP 429, honors a numeric or HTTP-date Retry-After as the minimum wait, and uses bounded exponential fallback. Retry timers are coalesced with live invalidations and cleared on unmount, manual retry, or Viewer change. The card keeps its current authorization checks.

Files: apps/web/src/lib/canvas/cards.ts, CanvasItemCard.svelte, cards.test.ts.

Validation output:

Test Files  1 passed (1)
Tests       18 passed (18)
Canvas cards: URI drop, event persistence, viewer resolution, placeholder, mode, Copy link, open, Task completion over SSE and twelve macOS screenshots passed.

Screenshots attached:
390 light card, 390 dark card, 820 light card, 820 dark card, 1440 light card, 1440 dark card.
390 light task, 390 dark task, 820 light task, 820 dark task, 1440 light task, 1440 dark task.

Decision: a valid server Retry-After remains a lower bound, even when it exceeds the 30-second fallback cap. Exponential fallback is capped at 30 seconds. The store makes at most four automatic retries, then offers manual Retry.

Head: 755fecd479.

Finished #1072 on branch job/7cfix-small, based on 357c856e3. The Canvas card store now shows a retrying status after HTTP 429, honors a numeric or HTTP-date Retry-After as the minimum wait, and uses bounded exponential fallback. Retry timers are coalesced with live invalidations and cleared on unmount, manual retry, or Viewer change. The card keeps its current authorization checks. Files: apps/web/src/lib/canvas/cards.ts, CanvasItemCard.svelte, cards.test.ts. Validation output: ```text Test Files 1 passed (1) Tests 18 passed (18) Canvas cards: URI drop, event persistence, viewer resolution, placeholder, mode, Copy link, open, Task completion over SSE and twelve macOS screenshots passed. ``` Screenshots attached: [390 light card](https://git.kayg.org/attachments/47f688d3-f096-41bf-9e14-288e8c2ff5a7), [390 dark card](https://git.kayg.org/attachments/6ac7df5f-4954-477b-ae13-98dbc110502b), [820 light card](https://git.kayg.org/attachments/f1d4dbcb-0bc7-488c-9d8c-8417c4b82e97), [820 dark card](https://git.kayg.org/attachments/8070a1d7-195c-47a4-99af-9dff5a760e4e), [1440 light card](https://git.kayg.org/attachments/8f53e901-bdcb-49e7-99de-11d16d39342d), [1440 dark card](https://git.kayg.org/attachments/9da99548-c5c0-41ef-a3a5-5bddb6e1a4bd). [390 light task](https://git.kayg.org/attachments/023defc2-1f35-42cc-9a73-229980a65dda), [390 dark task](https://git.kayg.org/attachments/b059f749-4997-4986-85b5-c650170082f9), [820 light task](https://git.kayg.org/attachments/41bf8718-1d77-42f0-b9bc-431f0178c8cd), [820 dark task](https://git.kayg.org/attachments/a97f8b5b-a408-4e62-bcfb-a65bd1b7d465), [1440 light task](https://git.kayg.org/attachments/8379943d-bb15-4436-aab3-e4df2bee6316), [1440 dark task](https://git.kayg.org/attachments/3e6e50de-a82c-4822-9183-7e77f10613ae). Decision: a valid server Retry-After remains a lower bound, even when it exceeds the 30-second fallback cap. Exponential fallback is capped at 30 seconds. The store makes at most four automatic retries, then offers manual Retry. Head: 755fecd4795661327aa72a2232b17dacaef32ade.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#1072
No description provided.