feat(desktop): state diagnostics — render + store churn counters - #71925
Merged
Conversation
Adds two dev-only counters that attribute re-renders and store notifications during an interaction, so a perf claim can be answered with a number instead of a hunch: window.__RENDER_COUNTS__ what re-rendered, and why (props/state/parent) window.__ATOM_CHURN__ which store published it, and whether it mattered The `wasted` column in each is the fix list — components that re-rendered with no changed input, and stores that published a value equal to the last one. Both are inert until start(), so idle cost is one branch per commit and per notify. React 19.2 removed injectProfilingHooks from react-dom, so the mark* profiling family is unavailable and onCommitFiberRoot is the only channel left. <Profiler> can't answer the question either: React invokes onRender for every Profiler in a committed tree including subtrees that bailed out, and a bailed-out subtree still reports nonzero actualDuration. This uses bippy's didFiberRender instead. bippy over react-scan because react-scan/lite is a thin wrapper over it while the package pulls ~217 transitive deps and floats two on latest. main.tsx imports the entry statically above react-dom because react-dom captures the devtools hook at module init — a late install reports renderers=0 and observes zero commits. Production exclusion is handled by a build-time alias to a no-op module rather than tree-shaking, since a static side-effect import can't be eliminated.
Drives the same synthetic multi-tab streaming workload as `multitab` (publishSessionState per session per flush, no backend, no credits) but reports render attribution instead of frame pacing — sidebar_renders, wasted_renders, and wasted_notifies. Answers 'does the sidebar re-render while an agent is typing' directly.
Contributor
૮ >ﻌ< ა ci reviewran on 37ac8ae ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence upload failed. Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso) |
OutThisLife
enabled auto-merge
July 26, 2026 19:11
teknium1
approved these changes
Jul 26, 2026
This was referenced Jul 26, 2026
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…ate-diagnostics feat(desktop): state diagnostics — render + store churn counters
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.
Instrumentation for diagnosing renderer state churn, built ahead of the streaming/multi-tab performance work. It turns "I think the sidebar re-renders while an agent is typing" into a number.
Two dev-only counters, plus a perf scenario that drives them:
The
wastedcolumn in each is the fix list — components that re-rendered with neither changed props nor changed hook state, and stores that published a value deep-equal to the last one.@nanostores/reactbails out on reference equality only, so the latter re-renders every subscriber for nothing; that's the "preserve reference identity on no-ops" rule inapps/desktop/AGENTS.md, now measurable.Both are inert until
start(), so the idle cost is one no-op branch per commit and per notify.First result
Against 5 concurrent streaming tabs (
render-churn --spawn --tiles 5 --tokens 240), reproducible across runs:The sidebar hypothesis is refuted — 6 renders across the whole run, all on genuine busy/needsInput edges, none wasted. The
stableArrayguards on$workingSessionIds/$attentionSessionIds(store/session-states.ts:236-259) are holding.$sessionStatesnotified 1,200 times across 10 listeners with zero wasted notifies, so the publishing side is honest too.The real cost is in components re-rendering on a parent commit without their own inputs changing:
TooltipContent2,048 wasted of 2,304,StatusbarItemView1,446 of 2,174,BlockandCt880 of 880 each. That's the next PR, not this one.Why bippy
React 19.2 removed
injectProfilingHooksfrom react-dom (grep -c→ 0), so the entiremark*profiling family is dead on this stack andonCommitFiberRootis the only channel left.<Profiler>cannot answer the question: React invokesonRenderfor every Profiler in a committed tree including subtrees that bailed out, and a bailed-out subtree still reports nonzeroactualDuration, so there's no safe threshold. Counting those callbacks would have "proven" the sidebar hypothesis true. This usesdidFiberRenderinstead.react-scan ships the right idea in its
react-scan/litesubpath, butlite's only imports arebippyandbippy/source, while the package pulls ~217 transitive deps and floatsreact-grab/react-doctoronlatest(non-reproducible installs; its main entry currently breaks Vite — upstream #448, #467). Taking bippy directly: MIT, zero dependencies.Import order
main.tsximports the entry statically, abovereact-dom. react-dom captures the devtools hook at module init, not atcreateRoot:_renderersA dynamic import behind
import.meta.env.DEVresolves a microtask too late. Production exclusion is therefore a build-time alias to a no-op module rather than tree-shaking, since a static side-effect import can't be eliminated. Verified against the built bundle:bippy,__RENDER_COUNTS__,__ATOM_CHURN__,traverseRenderedFibers,didFiberRenderall → 0 occurrences.