fix(table): typewriter full-text flash on table workflow column#4694
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview The animation now uses a start-time ref plus a lightweight state “frame” tick to trigger re-renders, and adds guards for Reviewed by Cursor Bugbot for commit e4f02aa. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR fixes an intermittent full-value flash in the
Confidence Score: 5/5Safe to merge; the change is a targeted, well-reasoned fix to a single hook with no external side-effects. The switch from No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant React as React (Concurrent)
participant Render as Render Phase
participant State as State (prevText, revealed)
participant Ref as animateRef (ref)
participant Effect as useEffect [text]
note over React: text prop changes A → B
React->>Render: Start render (may be discarded)
Render->>State: "prevText !== text → setPrevText(B), setRevealed('')"
Render->>Ref: "animateRef.current = true"
React-->>Render: Discard render (concurrent preempt)
note over State: State rolls back (prevText=A, revealed=null)
note over Ref: Ref keeps mutation: animateRef=true
React->>Render: Committed render
Render->>State: "prevText (A) !== text (B) → setPrevText(B), setRevealed('')"
Render->>Ref: "animateRef.current = true (re-set)"
React->>Render: "Re-render (state settled: prevText=B)"
Render-->>React: "prevText===text, no-op"
React-->>React: "Commit with revealed=''"
React->>Effect: Fire (text dependency changed)
Effect->>Ref: "animateRef.current? true → animateRef=false"
Effect-->>React: Start rAF typewriter animation
Reviews (1): Last reviewed commit: "fix(table): typewriter prevText in state..." | Re-trigger Greptile |
…lash) The reveal used a lagging nullable `revealed` state with a `revealed ?? kind.text` fallback in the caller. Under React 18 concurrent rendering a committed render could observe `revealed === null` while `text` was the full value, so the fallback painted the entire string for one frame before the type-on — an intermittent flash, reproducible on a large Run-all (verified in-browser: 60+ cells flashing). Derive the revealed slice from `text` + elapsed time during render instead of holding it in state. For a non-null value the result is never `null` and never the full string on the frame `text` changes (elapsed ≈ 0 → 0 chars), so the fallback can't fire. `prevText` is tracked in state (not a ref) so a discarded render rolls it back and the change is re-detected on the committed render. Verified via DOM MutationObserver: 0 flashes across 213 animated cells.
74171ca to
e4f02aa
Compare
Problem
The typewriter intermittently flashed the full value for one frame before typing it out. Reproducible during a large Run-all (verified in-browser: 60+ cells flashing per run).
Root cause
The reveal held a lagging nullable
revealedstate, and the caller rendersrevealed ?? kind.text. Under React 18 concurrent rendering, a committed render could observerevealed === nullwhiletextwas the full value (the render-phase reset to''didn't reliably win the race), so the?? kind.textfallback painted the entire string for a frame before the type-on. Captured directly with a render-level probe: a committed render withtext.length = 74,revealed = null.Fix
Stop holding the revealed text in nullable state with a full-text fallback. Derive the slice from
text+ elapsed time during render:nulland never the full string on the frametextchanges (elapsed ≈ 0 → 0 chars), so the?? kind.textfallback can't fire → the flash is structurally impossible.prevTextis tracked in state (not a ref) so a render React starts then discards rolls it back and the change is re-detected on the committed render.Verification
Instrumented the live build with a DOM
MutationObserverover the grid:0 → <full> → 0 → 1 → 2 …(flash).0 → 1 → 2 …grows.tsc + lint clean.
Type of Change
Checklist