Version. 0.1
Date. 2026-08-07
Author. Claude Code (Wave 0 session). Operator: Marvin Percival.
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1.
Scope. Wave 0 investigation 0.1 of standing-notes/loomworks-development-schedule-v0_1. Read-only. No code changed.
Read against. Surface loomworks at baec093, clean tree. Source read only; nothing executed in a browser.
Nothing a user would call data is lost — no scroll state, no cursor, no draft text, no pagination, no refetch. What is lost is keyboard focus, on every re-render. The item's framing ("whether anyone loses anything") resolves to: an accessibility defect and a wasted-work cost, not a data-loss defect. Scroll position is the one thing that could not be settled by reading and needs a single live test.
All four are declared inside the body of export function Home() (Home.tsx:97), so each is a new function identity on every render, which makes React unmount and remount the entire subtree rather than update it.
| # | Component | Defined | Rendered |
|---|---|---|---|
| 1 | EngagementsZones | Home.tsx:198 | Home.tsx:301 |
| 2 | NeedsView | Home.tsx:227 | Home.tsx:303 |
| 3 | RecentView | Home.tsx:258 | Home.tsx:304 |
| 4 | LensBody | Home.tsx:291 | Home.tsx:402, Home.tsx:481 |
All in src/app/operator/engagement-navigation/Home.tsx. Two in-code acknowledgements already exist: eslint-disable-next-line react-hooks/static-components at Home.tsx:401 and Home.tsx:480.
Verified as exactly four, not "at least four." A repo-wide sweep for nested component declarations (^ (function|const) [A-Z]… across src/**/*.tsx) returns these four and one non-instance: TopBar.tsx:243's const Icon = isNewest ? IconSortDescending : IconSortAscending, which aliases an already-imported stable component rather than defining a new one, so React sees a stable type. There is no fifth.
Every hook holding real state is called in Home itself, outside the four. Confirmed by reading Home.tsx:97–197:
| State | Where held |
|---|---|
| useHomeData() | Home.tsx:105 |
| usePagedList (needs) | Home.tsx:115 |
| usePagedList (recent) | Home.tsx:120 |
| lens, hint (useState) | Home.tsx:126, :127 |
| touchRef (useRef) | Home.tsx:128 |
| useVoiceListening | Home.tsx:149 |
Home is not remounted — only its four children are. A grep for useState|useRef|useEffect|usePagedList|useHomeData inside the four's own bodies (Home.tsx:198–309) returns no hook calls at all (one comment mentions useHomeData by name).
The rendered subtree is entirely pure. HomeCards.tsx and components/lists/LoadMoreControl.tsx were grepped for useState|useRef|useEffect|<input|<textarea: zero matches in either file. Every interactive leaf is a <button>.
Consequences, each following directly:
usePagedList in Home.<input> or <textarea> anywhere in the subtree.
A remount destroys the focused DOM node, so document.activeElement falls back to <body>.
The sharpest case is self-inflicted. Clicking "Load more" sets loading state inside usePagedList, which re-renders Home, which gives NeedsView/RecentView new identities, which destroys the very <button> the user just activated (LoadMoreControl.tsx:53). A keyboard user cannot press Enter again without re-navigating to it. Mouse users will not notice.
A second, higher-frequency trigger. useVoiceListening's onresult sets transcript state on every interim speech result, so while the mobile mic is live, Home re-renders many times per second and the entire visible list DOM is torn down and rebuilt each time. This is a wasted-work cost rather than a correctness one, but it is the largest one here.
Scroll position: mechanism identified, effect genuinely uncertain — flagged, not asserted. The mobile scroll container (Home.tsx:471, overflow-y-auto) is the parent of LensBody and survives; only its children are removed and re-inserted. Whether the browser clamps scrollTop during that commit depends on whether layout is flushed while the container is momentarily empty, which source reading cannot decide.
One live test settles it: on mobile, scroll the Needs lens down, click "Load more", observe whether scroll position holds. Recommend running that before scoping any fix.
The schedule gates CR-2026-188 on this finding: "only drafted if 0.1 finds something real is lost."
Something real is lost, but it is smaller and different from what the item assumed. The honest scoping input:
Home and pass what they need as props. They already read only values Home holds, so there is no state to relocate.LensBody must be hoisted for any fix to work. It is the cascade root: because it is itself a new type each render, its whole subtree remounts regardless of what the other three do. Fixing 1–3 alone changes nothing.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-51 findings — v0.1 — 2026-08-07