DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path inspection-briefs/loomworks-b51-findings-v0_1.md

Loomworks — B-51 findings: four components rebuilt on every change — v0.1

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.


Verdict, in one line

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.


1. The four, confirmed as exactly four

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.

2. Why nothing stateful is lost — the structural fact that decides it

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:

3. What IS lost: keyboard focus

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.

4. The one thing reading could not settle

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.

5. What this means for Wave 3

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:


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-51 findings — v0.1 — 2026-08-07