Skip to content

feat(web): Minimal interface with edge tool rails, and remount-free panel toggles - #680

Merged
danyaberezun merged 1 commit into
mainfrom
modern-interface-design
Oct 9, 2026
Merged

danyaberezun merged 1 commit into
mainfrom
modern-interface-design

Conversation

@danyaberezun

@danyaberezun danyaberezun commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The workbench's visual language had drifted behind contemporary agentic IDEs: tool panels were tab strips with heavy framing and the start screen (Project Home) used a different chrome than the workspace view. Separately, every click on an edge rail or panel toggle re-rendered the whole application: each fold/hide/show of any region remounted every ResizablePanelGroup and everything inside it — chat transcript (all turns re-parsed), editors, terminals, tool panes — ~1,900 component mounts and 45–65 React commits per click, and a mere focus event into the composer re-rendered the entire workbench chrome.

Approach

Minimal direction, style not UX. After a source-linked study of T3 Code, Conductor, Vibe Kanban, Claude Code desktop, Codex, Cursor, VS Code, IntelliJ, Zed and Linear (an isolated research lab with mockups of six directions and three style skins; kept out of the repository — design/ is gitignored — because it is a research artefact with its own tokens and fonts, not product code), the user chose the Minimal treatment with IntelliJ-style edge tool rails. The workbench arrangement, resource tabs and workflows are unchanged: Projects / Specs / Files / Changes / Review move to persistent edge rails (Fill icon + accent edge when shown, activate again to hide), terminal groups keep named session tabs, and Project Home gets the same rail + plain pane header through primitives exported from shell/layout, so the start screen and the workspace share one chrome vocabulary.

A theme is a palette. The standard Light/Dark and both High Contrast themes render exactly as on main. The quieter Minimal palettes (neutral surface hierarchy, restrained green, neutral bubbleAccent) ship as two additional themes — Minimal Light and Minimal Dark — selectable in Appearance. Role usage never varies by theme: no component branches on the active theme or its contrast metadata to drop a fill or a border, and what a manifest key cannot express (frameless bubbles, fill-less selection) is a product decision for all themes rather than a theme — so those Minimal-study ideas were deliberately not shipped. themes/SPEC.md records the rule.

No remount on shape changes. react-resizable-panels 2.1.9 reconciles added/removed panels in place when panels carry id+order, so the projectionEpoch/topology keys on every panel group were unnecessary. Panels now change membership in place, the bottom column is one always-mounted vertical group with a conditional bottom panel, committed sizes are re-applied imperatively, and topology-dependent minSizes are relaxed for the single membership-change commit (useTopologySettled) because the library re-evaluates a panel's constraints against its not-yet-recalculated layout and otherwise throws (Panel data not found) on alignment changes. Focus events no longer write identical attention objects (sameLayoutAttention), useWorkspaceRead.reload is memoized, and tool-pane elements are built once per workspace so tool bodies bail out.

The rail mirrors the panes; tools and terminals never share one. A first review of the rails against the layout grammar found that the group model every gesture runs on had become invisible: the rail was one flat icon list per side, the pane header showed only the selected tool's name, the only drag handle was the 32 px edge icon, and a tool dropped into a pane silently replaced the one on screen. Rather than change the frame model (IntelliJ's two-slot sides) or bring tab strips back, the rail now renders the model: icons come in clusters, one per pane in document order, separated by a hairline; one pressed icon per open pane; removed tools sit dimmed in a trailing cluster. Drops follow the clusters — an entry's halves insert into that pane, a New pane here zone at every cluster boundary creates one — and the pane header title is a drag handle for its tool, so a user who looks at the pane finds something to grab. Mixed tool + terminal panes are gone (canJoinAuxiliaryGroup, enforced in every mutation, in normalization on load and across workspaces, in passive placement and preset remaps); with them went the mixed-order anchors (beforeToolByTabId) and terminal recall (lastTerminalByGroup) — a terminal pane's selection is its recall. Menus name a pane by its contents (Move to right pane (Changes, Review)), hide commands that can never apply to a tab, and the layout owns New terminal / New terminal pane below. layout/SPEC.md records the rules; the analysis that led here is summarised in the PR discussion.

Rebased on main (#659, #663 and #682 have landed). The rails live in #682's split renderer: workbenchRails.tsx (region rail + droppable entry), AuxiliaryGroupHeader in workbenchSide.tsx, the settle/enforce/focus hooks and rail DOM ids in workbenchShared.tsx. Three deliberate adaptations to #682's render-isolation model are documented in layout/SPEC.md: the rail receives attention explicitly like a stack (its entries render from selection), the pane header's terminal strip selects through onSelectTab, and onUserNavigation is back on SharedGroupProps as a stable callback. e2e/perf/layoutIsolation.perf.ts still passes. The README and its screenshots are left as main has them; they should be re-captured at retina size after this lands.

Changes

  • apps/web/src/shell/layout: persistent AuxiliaryRails rendered as per-pane clusters (clusterAuxiliaryRail, hairline separators, dimmed unplaced cluster) with "New pane here" boundary drop zones; plain AuxiliaryPaneHeader whose title drags its tool, + Hide control; workbenchRails.tsx; canJoinAuxiliaryGroup / SEPARATE_PANES_REASON / describeLayoutGroup; normalizeWorkspaceView; structural-only panel-group keys, useEnforcedLayout, useTopologySettled; SPEC gains Shape changes without remounts, the pane-cluster rail grammar, the tool/terminal pane rule and the module structure.
  • apps/web/src/shell/ProjectHome.tsx (new) replaces the old welcome branch and CollapsedPanelRail; useCollapsibleRegion is one toggle shared by the rail entry, Hide and Mod+B.
  • apps/web/src/shell/layoutState: applyLayoutAttention drops no-op writes; lib gains sameLayoutAttention.
  • apps/web/src/shell/WorkspaceWorkbench.tsx: tool bodies memoized per workspace; panels/useWorkspaceRead.ts: stable reload; renderSideMenuActions removed — the layout renders New terminal / New terminal pane below itself through onNewTerminal(groupId, area, { newPaneBelow }) → addTerminal → place-terminal intent.
  • Themes: themes/bundled/minimal-dark.theme.json and minimal-light.theme.json added; dark/light/both High Contrast manifests byte-identical to main. colors.json gains the feedback-info-muted / feedback-warning-muted steps the settled-reason badges and topbar chips use. Selection on tabs and rail entries is control-bg-selected + accent marker in every theme (no per-theme selectors). Dead animate-fade-in removed.
  • E2E: new tool-rails (cluster order, boundary vs insert drops, header drag, tool-over-terminal refusal, dimmed unplaced restore, fold/reload of a terminal pane's selection), rail-layout-regressions, project-home-chrome specs (incl. a DOM-continuity guard that fails if any panel group remounts); mixed-pane journeys in layout, bottom-panel, close-quit-shortcuts, workspace-tabs rewritten to the pane grammar; theme.spec.ts pins the selected-tab/rail-entry surface and marker under every manifest; perf:render gains a region-toggles scenario; review.spec.ts gains the composer-closed wait its sibling already had (pre-existing flake).
  • Measured before the rebase onto refactor(web): split Workbench and isolate layout rerenders #682 (fiber tally on the profiling build, before → after): fold a right group 49 → 4 commits and 1,896 → 1 mounts; Hide (×) a pane 62 → 17 / 1,940 → 17; hide left side 55 → 10 / 2,073 → 1; hide bottom 51 → 9 / 1,843 → 21; click into the composer 538 → 52 component updates. perf:render six-toggle scenario 83.7 ms → 29.8 ms median.

Screenshots

Same isolated fixture, 1600×1000, bottom terminal hidden in both. Before = main (830de941), after = this branch. The standard palettes are identical on both sides by design — only the chrome changes.

Scenario Before After
Workbench, Dark Workbench before, dark Workbench after, dark
Workbench, Light Workbench before, light Workbench after, light
Project Home, Dark Project Home before, dark Project Home after, dark

Rail grammar (after only — the rail is new): dragging Files paints "New pane here" zones at every pane boundary, and dropping on one opens a third pane; the right rail shows three clusters.

Dragging onto a boundary Three panes, three clusters
Dragging Files onto a rail boundary Three right panes with clustered rail

New themes (no "before" exists):

Minimal Dark Minimal Light
Workbench, Minimal Dark Workbench, Minimal Light

Related issues

None.

Checklist

  • Fast gates pass: bun run lint, bun run typecheck, bun run test
  • E2E suite passes for app-affecting changes (bun run e2e, or bun run e2e:full when touching agent behavior)
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

Testing

On the final tree:

  • bun run lint → clean (1443 files); bun run check:deps / check:boundaries / check:seams / check:spec-surface → OK; cd apps/web && bun run typography:check && colors:check && spacing:check && provider-glyphs:check → OK
  • bun run typecheck --filter='!@thinkrail/desktop' → 19 tasks green. The desktop task (electrobun prepare && tsc) cannot run on this machine while the ThinkRail dev app holds the electrobun build lock; no desktop file changes in this PR — CI covers it.
  • bun run test → 20 packages green (web 1615; server 1401 when run alone — two agentSessionManager cases are load-flaky under the parallel turbo run and are byte-identical to main)
  • bun run e2e (complete no-agent suite) → all 8 shards passed
  • bun run perf:render --runs 1 e2e/perf/layoutIsolation.perf.ts (refactor(web): split Workbench and isolate layout rerenders #682's isolation proof) → 2 passed

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the workspace-archive-research branch from 5f57262 to 935502d Compare October 8, 2026 16:15

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

Base automatically changed from workspace-archive-research to main October 9, 2026 10:03
@danyaberezun
danyaberezun force-pushed the modern-interface-design branch from 11cedb5 to 62fe5ce Compare October 9, 2026 14:17

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

danyaberezun added a commit that referenced this pull request Oct 9, 2026

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the modern-interface-design branch from 3b04ff2 to 9b921d9 Compare October 9, 2026 21:03

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the modern-interface-design branch from 9b921d9 to 9d291cf Compare October 9, 2026 21:06

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun force-pushed the modern-interface-design branch from 9d291cf to ae9def2 Compare October 9, 2026 21:06

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — please address the blocking inline finding.

Comment thread apps/web/src/shell/layout/AuxiliaryRail.tsx
…, and remount-free panel toggles

Projects, Specs, Files, Changes and Review move from per-pane tab strips to
persistent left/right/bottom edge rails. The rail renders the layout's group
model instead of hiding it: entries come in clusters, one per pane in
document order, separated by a hairline; one pressed icon per open pane;
removed tools sit dimmed in a trailing cluster. Drops follow the clusters
(entry halves insert into that pane, a "New pane here" zone at every
boundary creates one) and the pane header title drags its tool. Terminal
groups keep named session tabs, and Project Home composes the same rail and
pane header.

Tools and terminals never share a pane (canJoinAuxiliaryGroup): enforced in
every mutation, in normalization on load and across workspaces, in passive
terminal placement and preset remaps. The mixed-order anchors
(beforeToolByTabId) and terminal recall (lastTerminalByGroup) go with it.
Menus name a pane by its contents, hide commands that can never apply to a
tab, and the layout owns New terminal / New terminal pane below.

Panels no longer remount on shape changes: structural-only panel-group keys,
committed sizes re-applied imperatively, and topology-dependent minimums
relaxed for the one membership-change commit (useTopologySettled).

Themes: the standard Light/Dark and High Contrast palettes are unchanged; the
quieter Minimal palettes ship as Minimal Light / Minimal Dark. A theme is a
palette and role usage never varies by theme.

The rails live in the split workbench renderer (workbenchRails.tsx and
friends); the interface design lab that produced the direction stays out of
the repository (design/ is gitignored).

The spec-graph index also skips the scratch directories other assistants keep
in a project (.claude, .superpowers, .gsd, .mockups, .playwright-mcp), whose
plans and generated skills carry spec-shaped frontmatter.

README screenshots are re-captured from this head (the real project in an
isolated host: workbench light/dark and the Specs tool), with alt text that
names the edge rails.
@danyaberezun
danyaberezun force-pushed the modern-interface-design branch from ae9def2 to 6f46f6b Compare October 9, 2026 21:39

@jetbrains-air jetbrains-air Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — ready to merge.

@danyaberezun
danyaberezun added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 57a1361 Oct 9, 2026
17 checks passed
@danyaberezun
danyaberezun deleted the modern-interface-design branch October 9, 2026 21:59
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.

1 participant