feat(main-page): right-pane Files/Terminal tabs #23

Manually merged
lz merged 6 commits from feat/consolidate into main 2026-05-26 18:42:41 +02:00
Owner

Summary

  • Replace the full-screen terminal modal with a Files/Terminal tab strip in the main page's right pane. Terminal is the sticky default; the choice persists in localStorage.
  • Add a ⤢ control that promotes the terminal to today's full-screen layout via a CSS class toggle — xterm + WebSocket state survive intact (no remount, no scrollback loss).
  • Drop the per-session and per-workspace "term" buttons. The right-pane Terminal tab is the only entry point.
  • Debounce scope changes 250ms before mounting the terminal so rapid click-through doesn't spam WebSocket connect/disconnect cycles.

Implementation notes

  • New pure-logic helpers under nexus/src/lib/components/main-page/: right-pane-tab.ts (localStorage round-trip with 'terminal' default) and scope-debounce.ts (generic timer-coalescer). Both are vitest-tested in the node env without jsdom, matching the existing project convention.
  • SessionTerminal.svelte gains maximized + onShrink props (drops onClose). The outer wrapper's .fullscreen class is now a toggle rather than the only mode.
  • FileExplorerPanel.svelte gains an embedded prop so MainSplit can host the scope chip + back arrow in its own tab strip without duplicating them inside the panel; the /config-explorer caller is unchanged.

Test plan

  • pnpm exec svelte-check: 0 errors (2 pre-existing tabindex warnings unrelated)
  • pnpm vitest run src/lib/components/main-page/ src/lib/components/file-explorer/: 32/32 pass (12 new unit tests included)
  • First-load default = Terminal; localStorage key nexus.mainSplit.activeTab updates on click
  • Sticky preference: switch to Files → click a different session → still Files; reload → still Files
  • Rapid click through 5 sessions in <250ms → exactly one WebSocket opens
  • ⤢ then ↙ in a session terminal: scrollback intact, font picker still works, WebSocket not reopened
  • Escape in inline terminal = no-op; Escape in maximized = shrink
  • Workspace-scope terminal attaches to /api/workers/:id/terminal (boot pane)
  • Remove a session whose terminal is active → "— disconnected —" shown, row vanishes, no console errors
  • /config-explorer still shows the in-panel scope chip (regression check)
  • No "term" buttons remain anywhere

Out of scope

  • Split-pane view (both Files and Terminal simultaneously) — deliberately not designed for; tabs only.
  • Per-scope tab memory — the preference is sticky-global by design.

Known pre-existing failure (not from this PR)

src/lib/server/sessions/service.test.ts has 11 failing tests with SqliteError: table sessions has no column named kind. This failure also exists at the base commit 2caa986 and predates this work — the diff in this PR touches zero files under nexus/src/lib/server/ or nexus/migrations/.

## Summary - Replace the full-screen terminal modal with a Files/Terminal tab strip in the main page's right pane. Terminal is the sticky default; the choice persists in `localStorage`. - Add a ⤢ control that promotes the terminal to today's full-screen layout via a CSS class toggle — xterm + WebSocket state survive intact (no remount, no scrollback loss). - Drop the per-session and per-workspace "term" buttons. The right-pane Terminal tab is the only entry point. - Debounce scope changes 250ms before mounting the terminal so rapid click-through doesn't spam WebSocket connect/disconnect cycles. ## Implementation notes - New pure-logic helpers under `nexus/src/lib/components/main-page/`: `right-pane-tab.ts` (localStorage round-trip with `'terminal'` default) and `scope-debounce.ts` (generic timer-coalescer). Both are vitest-tested in the node env without jsdom, matching the existing project convention. - `SessionTerminal.svelte` gains `maximized` + `onShrink` props (drops `onClose`). The outer wrapper's `.fullscreen` class is now a toggle rather than the only mode. - `FileExplorerPanel.svelte` gains an `embedded` prop so `MainSplit` can host the scope chip + back arrow in its own tab strip without duplicating them inside the panel; the `/config-explorer` caller is unchanged. ## Test plan - [x] `pnpm exec svelte-check`: 0 errors (2 pre-existing tabindex warnings unrelated) - [x] `pnpm vitest run src/lib/components/main-page/ src/lib/components/file-explorer/`: 32/32 pass (12 new unit tests included) - [ ] First-load default = Terminal; localStorage key `nexus.mainSplit.activeTab` updates on click - [ ] Sticky preference: switch to Files → click a different session → still Files; reload → still Files - [ ] Rapid click through 5 sessions in <250ms → exactly one WebSocket opens - [ ] ⤢ then ↙ in a session terminal: scrollback intact, font picker still works, WebSocket not reopened - [ ] Escape in inline terminal = no-op; Escape in maximized = shrink - [ ] Workspace-scope terminal attaches to `/api/workers/:id/terminal` (boot pane) - [ ] Remove a session whose terminal is active → "— disconnected —" shown, row vanishes, no console errors - [ ] `/config-explorer` still shows the in-panel scope chip (regression check) - [ ] No "term" buttons remain anywhere ## Out of scope - Split-pane view (both Files and Terminal simultaneously) — deliberately not designed for; tabs only. - Per-scope tab memory — the preference is sticky-global by design. ## Known pre-existing failure (not from this PR) `src/lib/server/sessions/service.test.ts` has 11 failing tests with `SqliteError: table sessions has no column named kind`. This failure also exists at the base commit `2caa986` and predates this work — the diff in this PR touches zero files under `nexus/src/lib/server/` or `nexus/migrations/`.
lz added 6 commits 2026-05-26 18:03:40 +02:00
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(main-page): drop redundant term buttons (right pane is sole entry)
Some checks failed
ci / nexus (pull_request) Failing after 1m35s
ci / images (./nexus, agent-nexus) (pull_request) Has been skipped
ci / images (./worker, nexus-worker) (pull_request) Has been skipped
a6a70bf3e5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lz force-pushed feat/consolidate from a6a70bf3e5
Some checks failed
ci / nexus (pull_request) Failing after 1m35s
ci / images (./nexus, agent-nexus) (pull_request) Has been skipped
ci / images (./worker, nexus-worker) (pull_request) Has been skipped
to 18f8f5ab79
Some checks failed
ci / nexus (pull_request) Has been cancelled
ci / images (./nexus, agent-nexus) (pull_request) Has been cancelled
ci / images (./worker, nexus-worker) (pull_request) Has been cancelled
2026-05-26 18:16:30 +02:00
Compare
lz force-pushed feat/consolidate from 18f8f5ab79
Some checks failed
ci / nexus (pull_request) Has been cancelled
ci / images (./nexus, agent-nexus) (pull_request) Has been cancelled
ci / images (./worker, nexus-worker) (pull_request) Has been cancelled
to 54b7f87176
Some checks failed
ci / nexus (pull_request) Failing after 1m28s
ci / images (./nexus, agent-nexus) (pull_request) Has been skipped
ci / images (./worker, nexus-worker) (pull_request) Has been skipped
2026-05-26 18:17:55 +02:00
Compare
lz manually merged commit ff15cd031e into main 2026-05-26 18:42:41 +02:00
lz referenced this pull request from a commit 2026-09-15 23:01:28 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lz/agent-nexus!23
No description provided.