Skip to content

feat: bottom shell panel for quick commands #356

Description

@yicheng47

Motivation

Runner's premise is that you stop scattering agent work across terminal windows — but there's no way to run a quick git status, ls, or pnpm test without leaving the app or burning a chat tab on it. Today the options are bouncing out to Ghostty or ⌘T-ing a chat, and neither fits a ten-second command.

A VS Code-style bottom panel: a drawer that toggles open over the bottom of the content area, holds one or more plain shell PTYs, and persists across route changes so a long-running pnpm dev keeps going while you move between chats and missions.

No spec doc for now — filing to capture the idea. Write one if this grows past a single drawer.

Scope sketch

  • Toggleable bottom drawer with a keyboard shortcut (VS Code uses ⌃``; ⌘Jis also free — seesrc/lib/keymap.ts`), resizable by dragging its top edge, height persisted.
  • Holds plain shell sessions — the user's $SHELL, cwd defaulting to the active project's bound directory, falling back to home.
  • Survives route changes. This is the load-bearing requirement: the drawer must live above the router like the sidebar does, not inside the chat surface.
  • Multiple shell tabs within the drawer would be nice; one is enough for v1.
  • Not an agent surface. No runner config, no crew slots, no mission participation, no event-log involvement.

Implementation notes

Most of the session plumbing already exists:

  • "shell" is already the fallback runtime name for sessions with no agent runtime — runtime_for() returns it (src-tauri/src/commands/session.rs:39, asserted at :1426), and it appears throughout test fixtures (commands/runner.rs:634, repo/runner.rs:221, commands/slot.rs:525).
  • Non-catalog runtimes already degrade correctly: resume_plan falls through to ResumePlan::fresh() (src-tauri/src/router/runtime.rs:660), model_effort_args/permission_mode_args/first_turn_argv all return empty, and runtime_clears_on_resize is false — a shell keeps its ring on resize, which is the right behavior.
  • So the backend gap is a spawn entry point for "plain $SHELL, no agent adapter", not new session machinery.

Open questions:

  • Where the drawer mounts. It must sit outside <Outlet /> in src/components/AppShell.tsx:101 to survive navigation. PersistentSurfaces solves an adjacent problem (keeping the last chat + mission mounted) and its visibility:hidden-not-display:none lesson from impl 0032 applies directly — a hidden drawer must stay measurable or its PTY geometry goes stale.
  • Whether shell sessions become rows. Persisting them means they show up in the sidebar and archive lists, which is probably wrong for a scratch shell. Ephemeral (drawer-local, dies with the app) is likely the better v1 — but that forfeits the auto-resume-on-launch behavior from feat: auto-resume running chats and missions on app launch #320.
  • Whether "shell" gets a real RUNTIME_DEFINITIONS entry. It would make the drawer reuse the normal spawn path and show up in Settings → Agents — but it would also appear in the chat runtime picker and crew slot dropdowns, which isn't wanted. Probably stays out of the catalog with a dedicated spawn command instead.

Relevant code

  • src/components/AppShell.tsx:101 — the flex shell where a bottom drawer would mount.
  • src/components/PersistentSurfaces.tsx — precedent for keeping a surface mounted across routes.
  • src/components/RunnerTerminal.tsx — the terminal component the drawer would host.
  • src/lib/keymap.ts — shortcut registry.
  • src-tauri/src/commands/session.rssession_start_runtime is the closest existing spawn entry point.
  • src-tauri/src/router/runtime.rs — the non-catalog fallthrough behavior described above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Annoying — fix when convenientfeatureFeature work tracked in docs/features/

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions