Skip to content

fix: seed AnnotationRowPage form during render, not from an effect - #37

Open
duncankmckinnon wants to merge 1 commit into
mainfrom
fix/annotation-row-seed-during-render
Open

fix: seed AnnotationRowPage form during render, not from an effect#37
duncankmckinnon wants to merge 1 commit into
mainfrom
fix/annotation-row-seed-during-render

Conversation

@duncankmckinnon

Copy link
Copy Markdown
Owner

What broke

main's test workflow flaked on the web job: AnnotationRowPage.test.tsx:117 — "saves the checked labels and description" — failed intermittently with:

AssertionError: expected "spy" to be called with arguments: [ 'ls1', 'r1', { …(3) } ]
  "description": "looks solid",
-  "labels": ["good"],
+  "labels": [],

The tagged v0.2.0 release itself succeeded (PyPI + Homebrew tap both published) — this was a separate CI job on the same commit.

Root cause

AnnotationRowPage seeded its form (labels, value, description) from a useEffect keyed on the current row object. Since entries starts null and is fetched async, the commit that first paints the form is the same commit that queues the seeding effect — so the form is interactive before it's been seeded. Under CI load, a test's click landed in that window and got overwritten when the effect finally flushed:

  1. form commits, seed effect still pending
  2. click "good" → labels: ["good"]
  3. seed effect flushes → setLabels(annotation?.labels ?? [])[]
  4. description typed after the flush, so it survived
  5. Save sent labels: []

Fix

Seed the form during render instead of in an effect, keyed on row_id. React re-renders immediately on a render-phase state update, so the form is never committed un-seeded — this closes the race window rather than narrowing it.

Keying on row_id instead of the row object also fixes a second, user-visible instance of the same bug: a save's own response replaces entries, which was silently reverting an edit made while that save was still in flight.

Testing

  • Added a regression test for the in-flight-edit case (deterministic, unlike the original scheduler race). Verified it fails without the fix and passes with it.
  • npx tsc --noEmit clean
  • Full web suite: 51 files / 651 tests passed
  • Affected test file run 5× consecutively to check for flakiness

🤖 Generated with Claude Code

The form was seeded from an effect keyed on the current row object. Since
entries starts null and is fetched, the commit that first paints the form
is the same commit that queues the seeding effect, leaving a window where
the form is interactive before it's been seeded. On a loaded CI runner a
test's click landed in that window and got clobbered when the effect
finally flushed, producing an intermittent failure in
"saves the checked labels and description".

Keying the seed on row_id and doing it during render (not useEffect) closes
the window instead of narrowing it: React re-renders immediately on a
render-phase update, so the form is never committed un-seeded. Keying on
row_id rather than the row object also fixes a second, user-visible
instance of the same bug -- a save's own response replaces entries, which
was silently reverting an edit made while that save was still in flight.

Added a regression test for the in-flight-edit case, which is deterministic
(unlike the original scheduler race). Verified it fails without the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant