Skip to content

feat(ui): add an accessible VS Code-parity Tree - #1065

Draft
EhabY wants to merge 1 commit into
mainfrom
feat/ui-tree-suite
Draft

feat(ui): add an accessible VS Code-parity Tree#1065
EhabY wants to merge 1 commit into
mainfrom
feat/ui-tree-suite

Conversation

@EhabY

@EhabY EhabY commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a controlled, accessible Tree to @repo/ui that follows current VS Code workbench behavior and visuals.

  • Flat, data-driven row projection with aria-activedescendant
  • Independent focus and selection, including hidden selection behavior
  • Single selection, cleared by Escape before the key returns to the host
  • Arrow, Home, and End navigation, into and out of branches
  • Single-click and double-click expansion modes, twistie isolated from selection
  • Explorer styling, indent guides, row actions, high-contrast support, and Storybook coverage

Closes #1037

This is the first of three stacked PRs, kept to the behavior a tree needs to be
usable at all:

Adds Lines
this one rows, ARIA, navigation, expansion, single selection 2,548
#1079 multi-select, type navigation, paging, sticky scroll 1,494
#1078 row and action-bar hovers 689

Architecture

flowchart LR
  accTitle: Tree architecture
  accDescr: Data and input flow through pure Tree modules into the React and DOM adapter.

  Props[Nodes and controlled props] --> Model[treeModel.ts]
  Events[Pointer and keyboard events] --> Policy[treePolicy.ts]
  Policy --> Commands[Tree commands]
  Model --> Transition[treeTransition.ts]
  Commands --> Transition
  Transition --> Adapter[useTreeAdapter.ts]
  Adapter --> Rows[Tree.tsx and TreeRow.tsx]
Loading

treeModel.ts, treePolicy.ts, and treeTransition.ts are pure. useTreeAdapter.ts is the thin React and DOM boundary, which is what lets the two PRs above stack on this one without reshaping it.

The flat visible model supports future windowing, but this Tree is not currently virtualized.

API surface, checked against native

Every prop maps to something VS Code has, so the component stays a parity layer rather than a superset:

Prop Native counterpart
expandMode: "singleClick" | "doubleClick" workbench.tree.expandMode, same values
variant="explorer" the align-icons-and-twisties render mode
TreeNode.icon, .action TreeItem.iconPath, the row action bar

TreeNode.className is the one non-native field, kept as the hook for per-row color or emphasis. Webviews receive no workbench.tree.* settings, so a consumer that wants to mirror them reads them in the extension host and passes them down.

Two things deliberately do not exist, because native does not have them:

  • Disabled rows. TreeItem has no such field, and every aria-disabled in the workbench is an action item or a toggle, never a list row. Dim a row through className instead.
  • Actions gated on focus. A row's action bar stays live on plain hover, as in the native list. inert blocks hit-testing rather than only tab order, so gating it left a visible button that took neither a hover nor a click.

Divergences from native, and why

  • Modern UI by default. Webviews get no signal for workbench.experimental.modernUI, so the package picks the newer look and data-ui-style="stable" restores stable row geometry, focus behavior, and menu motion.
  • Escape handling. Selection clears first, then the focus mark, and only then is the key left to the host, so an embedding webview can still use it.

Diff composition

Measured against origin/main...HEAD with git diff --numstat:

Area Added Deleted
Production UI source 1,154 0
Automated tests and shared helpers 1,008 0
Stories and visual fixtures 254 1
Documentation 101 8
Build and workspace configuration 31 2
Total 2,548 11

Production source to automated test code is approximately 1.14:1.

Performance

The row projection replaced a composed-children tree with a DOM-querying store. 5,020 expanded rows in jsdom, median of three: mount 965ms to 695ms, twenty arrow keys 249ms to 89ms. Virtualization, if it is ever needed, is a windowed slice over the same projection.

Validation

  • pnpm test:webview --reporter=dot: 41 files, 349 tests passed
  • Focused Tree suite: 6 files, 31 tests passed
  • pnpm typecheck, pnpm lint, pnpm format:check, pnpm storybook:ci
  • Every component in packages/ui compiles under the React Compiler with no bailout, checked by running babel-plugin-react-compiler over the package. TreeRow used to bail out silently by reading other props from its parameter defaults; AGENTS.md and the package README now record the rule.
  • Row geometry, indent guides, and theme parity verified in Chrome against a running Storybook in all four captured themes
Implementation plan and decisions
  1. Keep the model, input policy, and interaction transitions pure.
  2. Keep React state, controlled callbacks, refs, event normalization, and DOM effects in one thin adapter.
  3. Render a flat visible row projection while retaining complete hierarchy data for hidden focus and selection behavior.
  4. Preserve VS Code selection, expansion, keyboard, accessibility, and visual behavior.
  5. Replace the external mutable store and context layers unless measurements justify them.
  6. Document the architecture with a compact Mermaid diagram.
  7. Split the large component test into focused core, row, and keyboard suites with shared helpers.
  8. Validate behavior, type safety, formatting, linting, builds, Storybook, test counts, and measured interaction performance.

This pull request description was updated by Coder Agents on behalf of @EhabY.

@EhabY EhabY self-assigned this Aug 5, 2026
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 510e466 to 309f05d Compare August 5, 2026 14:11
Base automatically changed from feat/ui-package-gaps to main August 7, 2026 10:52
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch 17 times, most recently from 3a55395 to 3fe1ebe Compare August 13, 2026 13:03
@EhabY EhabY changed the title feat(ui): add accessible tree suite feat(ui): add accessible VS Code-parity Tree Aug 13, 2026
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 3fe1ebe to 53d6612 Compare August 14, 2026 12:10
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch 3 times, most recently from fef2ca2 to 6fec430 Compare August 14, 2026 14:29
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
@EhabY EhabY changed the title feat(ui): add accessible VS Code-parity Tree feat(ui): add an accessible VS Code-parity Tree Aug 14, 2026
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 33e2c52 to 215c027 Compare August 14, 2026 15:15
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 215c027 to 85c00af Compare August 14, 2026 16:00
A controlled Tree that follows current VS Code workbench behavior: every
visible node renders as a flat `treeitem` row with declared aria-level,
posinset, and setsize, while keyboard navigation keeps DOM focus on the
container and names the active row with `aria-activedescendant`. Focus and
selection stay independent, as they do natively.

Arrow keys, Home, and End move the active row; Arrow Right and Left walk
into and out of branches; Enter, Space, and the twistie follow VS Code's
split between selecting and expanding, under either expand mode. Rows are
22px with the native twistie gutter and indent guides, and `variant="explorer"`
aligns leaf icons with branch twisties for icon-less file trees.

The model, the input policy, and the interaction transitions are pure
modules; `useTreeAdapter` is the only place React state and the DOM meet.
The flat projection leaves room for windowing later.

Closes #1037
@EhabY
EhabY force-pushed the feat/ui-tree-suite branch from 85c00af to cb58b18 Compare August 14, 2026 16:20
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
EhabY added a commit that referenced this pull request Aug 14, 2026
MVP for DEVEX-621: render the Workspaces panel in the experimental
coder.workspacesPanel webview using the @repo/ui Tree from #1065 so the
row and panel components can be exercised with mock data before the real
IPC wiring lands.

- WorkspacesPanel with SearchInput live filtering and the Mine / All /
  Shared WorkspaceFilterSelect (Shared gated by isOwner)
- Workspace and agent rows as rich Tree labels with status pills and
  hover-revealed inline actions
- Inline App Statuses and Agent Metadata sections under agents, with a
  collected-at tooltip on metadata values
- Loading / error / empty state panels over the mock data
- @repo/ui becomes a webview dependency; the shared Vite config aliases
  its TypeScript source and package-internal subpath imports

This pull request description was generated by Coder Agents.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui: Tree suite with native-fidelity look and ARIA keyboard nav

1 participant