Files
kandev/docs/specs/ui/system-design/task-surface-render-isolation.md
T

8.5 KiB

status, system, requirements
status system requirements
draft ui
REQ-UI-FILE-TREE-CHAT-CONTEXT-001
REQ-UI-SIDEBAR-TASK-ROW-PRESENTATION-001
REQ-UI-MOBILE-TASK-NAVIGATION-001

Task Surface Render Isolation System Design

Purpose and boundaries

The UI system owns render isolation for task surfaces with many repeated rows. This design covers the file tree, sidebar task rows, pull-request indicators, and task-top-bar plugin contributions.

The task, integration, and plugin systems still own their data and actions. This design does not change their contracts, persistence, or user-visible behavior.

The browser trace from 2026-08-31 showed three full render waves in 5.74 seconds. Each wave rendered approximately 602 file rows and 48 sidebar rows. Layout, paint, and garbage collection were minor costs, so this design targets React work and mounted component count.

Requirement mapping

Requirement Design sections
REQ-UI-FILE-TREE-CHAT-CONTEXT-001 File-tree rows, File-tree virtualization, Responsive behavior
REQ-UI-SIDEBAR-TASK-ROW-PRESENTATION-001 Sidebar rows, Derived contribution props, Responsive behavior
REQ-UI-MOBILE-TASK-NAVIGATION-001 Responsive behavior, Failure and recovery

Components and responsibilities

  • useFileBrowserTree owns stable file-tree state and action identities.
  • FileTreeView owns row-window calculation and the single file-tree scroll owner.
  • TreeNodeItem owns one file or directory row and skips semantically unchanged renders.
  • FileContextMenu preserves the established pointer, keyboard, and touch actions for mounted rows.
  • useSidebarSelection owns stable selection actions for the task switcher.
  • TaskSwitcher and TaskRow skip work when their rendered inputs do not change.
  • TaskTopBarPluginActions derives stable session identifiers for plugin slot props.
  • useTaskPRTooltipHydration returns a stable value while its status and actions do not change.
  • PRTaskIcon reuses stable empty and unchanged pull-request inputs.

Render identity contract

A parent render must not change a row callback when the callback behavior is unchanged. A hook must not return a new aggregate object when all fields retain the same identity and value.

List rows use memoized component boundaries. Each row receives the smallest required state. A row rerenders when its node, selection, expansion, active-file, drag, or action state changes.

Render-count tests prove identity behavior. The tests use controlled rerenders and do not use elapsed-time thresholds.

File-tree rows

File-tree actions read changing tree data from stable refs or functional state transitions. The callbacks do not depend on the complete treeState aggregate object.

TreeNodeItem uses a memoized boundary with explicit, stable inputs. Selection or expansion of one node does not rerender unrelated visible rows. File opening, drag and drop, rename, download, deletion, multi-selection, and chat-context actions keep their current behavior.

Each mounted row keeps its own context-menu trigger. A shared menu is not required for this change. Virtualization limits the number of mounted menu providers.

File-tree virtualization

FileTreeView uses the existing @tanstack/react-virtual dependency. The flattened visibleRows array remains the source for order, nesting depth, expansion, and filtering.

The existing file-tree viewport remains the only vertical scroll owner. The virtualizer positions only the current row window and a small overscan window inside that viewport.

Rows use measured sizes because editing controls and touch layouts can change row height. The virtualizer uses an estimated size before measurement. It updates positions after a measured size changes.

Only positive row measurements can replace cached sizes. A hidden panel can produce zero-height observer entries even though its rows remain connected. Those entries represent unavailable geometry, not a row with no content. The measurement callback retains the last positive size for the row key, or uses the current estimate when no positive measurement exists. When the panel becomes visible, normal measurement accepts its actual row sizes. This applies to node rows and inline creation controls in both pointer modes. It implements AC-UI-FILE-TREE-CHAT-CONTEXT-001.10 without another scroll owner or changes to Dockview panel lifetime.

Active-file reveal and restored scroll operations resolve a row index from visibleRows. They use the virtualizer scroll API before they query a row element. This sequence makes an offscreen row available before focus or selection work uses its element.

Sidebar rows

useSidebarSelection derives onBulkMove from stable action functions and current selection data. It does not depend on a newly allocated selection aggregate.

TaskSwitcher receives stable callbacks during unrelated parent renders. A memoized task row can then isolate row work when task data and row state do not change.

The task row remains the primary mobile tap target. The visible mobile action and fine-pointer hover behavior remain unchanged.

Derived contribution props

Plugin slot props use a stable session-identifier array while the ordered identifiers do not change. Session status updates cannot rerender the plugin contribution when its slot input stays equal.

Pull-request tooltip hydration returns one stable aggregate while its status, hydration action, and automation options stay equal. Pull-request indicators also reuse one empty array constant.

These changes do not alter plugin host APIs or integration data. They only stabilize values at UI component boundaries.

Control flow

  1. A store or local state update renders a task-surface owner.
  2. Stable selectors and callbacks preserve identities for unchanged inputs.
  3. Memoized boundaries skip unchanged rows and contributions.
  4. The file-tree virtualizer calculates the required row window from the scroll position.
  5. React renders only changed components inside the active row window.

Responsive behavior

Desktop and mobile use the same flattened file-tree model and virtual row window. The layout does not create a second vertical or horizontal scroll owner.

Each mounted mobile file row keeps its visible action trigger and 44 CSS pixel touch target. The responsive menu stays inside the viewport and keeps its safe-area behavior.

Each mobile sidebar row remains the primary navigation action. Its explicit task menu stays visible and touch-reachable. Virtualization does not change the sidebar composition.

Failure and recovery

If row measurement is unavailable, the virtualizer uses the estimated row size. If a target row is offscreen, reveal logic scrolls to its index before it requests the element.

An empty tree renders the current empty state. A changed filter or expansion state recalculates the flat row list and virtual range from the current scroll owner.

Identity optimizations must fail toward an extra render, not stale UI. Equality logic must include every input that changes visible output or interaction behavior.

Persistence

This design adds no persisted state. Existing expansion, selection, file, sidebar, plugin, and pull-request state keeps its current owner and lifetime.

Security

This design adds no trust boundary or data access. Virtual rows use the same authorized file and task data as the current complete list.

Observability

Unit tests record render counts for controlled updates. Browser E2E tests record the number of mounted rows for a large generated file tree.

Measurement tests cover positive sizes, unavailable sizes with a cached value, and unavailable sizes before the first measurement. Browser regressions hide and restore the Files panel, then compare visible row bounds with the viewport. Coverage includes compact desktop rows, touch rows, and scrolled large trees.

Final performance evaluation uses a warmed production build without React DevTools or CPU profiler startup in the measured window. The report includes main-thread work, long tasks, and mounted-row counts. Development traces remain diagnostic evidence only.

No ADR is required. This design applies the repository's existing virtual-list dependency and UI ownership model.