Repository navigation
fix(traces): stop the cold-start render loop in the trace store - #27
Merged
Merged
Conversation
- App passed `data?.traces ?? []`, a new array identity on every render; useTraceStore's sample-mode effect depends on that identity and sets state from it, so before the first snapshot arrived the pair looped until React's nested-update cap and logged "Maximum update depth exceeded" ~50 times per cold start - a module-level constant fixes the identity, and the effect now commits only when the row keys actually change, so an unstable caller cannot reopen the loop Tags: #traces #react #renderloop Co-Authored-By: htjulia <htjulia1@gmail.com>
htcom-code
added a commit
that referenced
this pull request
Aug 24, 2026
- oxlint 1.79.0's react plugin flagged four sites: three were real, one was dead code - module-detail-panel: the routes effect reset state when `moduleId` changed, so the previous module's routes stayed committed for one frame. The result now carries the module it belongs to and the view is derived from it, so a mismatch reads as "loading" - use-trace-store: sample mode is a pure function of the traces handed in, so it is derived during render. That removes the setRows -> render -> effect cycle an unstable `liveTraces` identity used to drive (#27) and the `sameRows` guard that existed only to break it - use-trace-store: the latest-rows ref is mirrored in an effect rather than written during render - use-persistent-state: the latest-value ref was never read (the setter is already stable through its functional update), so it is gone chore(lint): drop non-actionable virtualizer rule - react/incompatible-library reports `useVirtualizer` returning functions the React Compiler cannot memoize. We do not run the compiler, so the two warnings had nothing behind them; the config records the condition to re-enable Tags: #lint #react
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Every cold start logged
Maximum update depth exceeded~50 times before anydata arrived.
Apppasseddata?.traces ?? []intouseTraceStore— the??fallback builds a new array on every render, and the hook's sample-mode
effect (
src/hooks/use-trace-store.ts:52) lists that array in its dependenciesand calls
setRowswith rows derived from it. So: effect →setRows→ render →new
[]→ effect, until React's nested-update cap cut it off. It self-resolvedonce the first snapshot (or the sample fallback at 2.5s) gave
data.tracesastable identity, which is why the UI looked fine — it just burned ~50 render
passes and filled the console at each start.
Two changes:
App.tsxhands over a module-levelNO_TRACESconstant, so"no traces yet" has one stable identity.
passthroughonly whenthe row keys actually differ (
sameRows), so a future caller with an unstablearray cannot reopen the loop. Nothing in the render path can catch this class
of bug —
tscand oxlint both pass on the broken version.Live mode is untouched: that effect returns early when
persistis true.Type of change
fix— bug fix (no new behaviour)Correctness & conventions
src/lib/api.ts); components take plain props.src/lib/types.tsunchanged (no backend surface change).Verification
npm run lintpasses (3 pre-existingonly-export-componentswarnings, unchanged)npm run buildpasses (tsc -b && vite build)npm run dev, SAMPLE data, no platform on 8080):console is clean apart from the expected
502from the absent stream —Maximum update depth exceededwent from ~50 occurrences per load to 0arm + confirm empties the table to
0 shown · 0 retained, the "No traces yet"empty state renders, and the button disables
:8080in this environment. Thechanged effect is skipped entirely when
persistis true, so the liveseed/merge path is unaffected by construction.
Baseline for the counts above was an unmodified
mainworktree, which showed thesame error at 53 occurrences before this change.