Skip to content

fix(webui): split Models screen, harden overflow hook and format cache - #1977

Merged
inureyes merged 7 commits into
mainfrom
fix/issue-1976-models-split-overflow-cache
Sep 26, 2026
Merged

inureyes merged 7 commits into
mainfrom
fix/issue-1976-models-split-overflow-cache

Conversation

@inureyes

@inureyes inureyes commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Split ModelsLibrary (731 lines) along its seams into row-focus.ts (useRowFocus), columns.tsx (libraryColumns(context), LibraryRow, downloadRepo) and toolbar.tsx (LibraryToolbar). Behavior-preserving and in its own commit: screen.tsx is 433 lines, exports, DOM, class names, test ids and handler order are unchanged, and the existing Models unit and Playwright tests pass unmodified.
  • useOverflowRegion adds a MutationObserver on the region: it re-resolves the table, moves the ResizeObserver to it, and re-runs the overflow check, so a table mounted or swapped after attach (or content that grows without a resize) is followed. The observer watches childList only, and its overflow check is coalesced into one requestAnimationFrame, so row churn from polls and SSE bursts costs at most one layout read per frame.
  • A focused scroll box that stops overflowing keeps its tab stop, role and name until a one-shot blur, instead of dropping focus to <body>. disconnect removes both observers and the listener.
  • cached() is bounded at CACHE_LIMIT = 32 keys with oldest-first eviction; chat/time.ts uses the shared helper instead of its private unbounded copy.
  • The fix(webui): close residual LOW items in the Models and Activity pages #1975 technical report no longer cites a 500-line module rule; docs/code-guidelines.md sets 800, and the bullet now describes the cohesion concern this PR addresses.
  • Regenerated src/webui/assets (Node v26.5.1) and added the bilingual technical report under TECHNICAL_REPORTS/.

Changes during review

  • The implementation review found the mutation callback ran the layout-reading overflow check synchronously after every React commit that changed nodes, one forced layout per commit during an SSE burst (MEDIUM). It is now coalesced per frame; the attach-time and ResizeObserver paths stay synchronous. The security review found no CRITICAL or HIGH issues.

Test plan

  • New tests in common-adapters.test.tsx (late table, focused shrink) and format.test.ts (eviction) fail against the main code and pass here.
  • pnpm --dir webui run typecheck, lint, unit (35 node + 713 Vitest), theme selectors.
  • Full default Chromium Playwright suite: 124 passed on the final head.
  • build_bundle.py --verify, check_binary_assets.py, check_webui_contract.py.

Closes #1976

ModelsLibrary held page state, the row-focus effect, the action handlers, the column renderers, the filter toolbar, and the page JSX in one 731-line function. Move the seams out for cohesion and reviewability: useRowFocus() owns the focus-follow refs, effect and callback-ref registry; libraryColumns(context) builds the table columns from one typed context; LibraryToolbar renders the search field and three filters. screen.tsx is now 433 lines.

Behavior-preserving: the focus effect keeps its position after the inspector effects, and exports, rendered DOM, class names, test ids and handler order are unchanged. The existing Models unit tests pass unmodified.

Refs #1976
useOverflowRegion resolved the region's table once at attach, so a table mounted or swapped later was never observed for content growth. A MutationObserver on the region (childList, subtree) now re-resolves the table, moves the ResizeObserver to the new one, and re-runs the overflow check on every content change.

A focused box that stopped overflowing lost its tabindex, and the browser's focus fixup dropped focus to <body>. It now keeps its tab stop, role and name while focused and settles on a one-shot blur listener. disconnect tears down both observers and the listener.

cached() had no size bound. Each map now holds at most CACHE_LIMIT (32) keys, evicting the oldest on insert. chat/time.ts drops its private unbounded copy and uses the shared helper.

New tests in common-adapters.test.tsx and format.test.ts fail on main and pass here.

Refs #1976
The Remaining items bullet said screen.tsx exceeded a 500-line module guideline. docs/code-guidelines.md sets the threshold at 800 lines; the file was a cohesion concern, addressed by #1976. Both the English and Korean reports are updated.

Refs #1976
@inureyes inureyes added status:review Under review type:bug Bug fixes, error corrections, or issue resolutions priority:low Low priority area:architecture Architecture and code structure changes labels Sep 26, 2026
The MutationObserver added for #1976 called update() synchronously in its callback, which runs as a microtask right after each React commit. update() reads scrollWidth and scrollHeight, so every commit that added, removed or reordered nodes in the Models table or the Activity slot table forced a layout before passive effects ran, once per commit in an SSE burst.

The callback now re-resolves and re-observes the table synchronously (querySelector does not touch layout) and schedules a single requestAnimationFrame for the overflow check, so all commits in one frame share one read. disconnect cancels a pending frame. Without requestAnimationFrame it falls back to the direct check. The ResizeObserver path and the attach-time check stay synchronous.

The late-table test waits one frame before asserting the region role; it still fails against main's hook. Typecheck, lint and the design-system, Models, Activity and chat unit tests pass.

Refs #1976
@inureyes inureyes added status:done Completed and removed status:review Under review labels Sep 26, 2026
@inureyes
inureyes merged commit 44f17d8 into main Sep 26, 2026
20 checks passed
@inureyes
inureyes deleted the fix/issue-1976-models-split-overflow-cache branch September 26, 2026 03:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:architecture Architecture and code structure changes priority:low Low priority status:done Completed type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(webui): split Models screen, harden overflow hook and format cache

1 participant