Skip to content

Terminal: first-class workspace feature with Claude Code integration#118

Merged
jsgrrchg merged 21 commits into
jsgrrchg:mainfrom
spamsch:feature/terminal-first-class
May 21, 2026
Merged

Terminal: first-class workspace feature with Claude Code integration#118
jsgrrchg merged 21 commits into
jsgrrchg:mainfrom
spamsch:feature/terminal-first-class

Conversation

@spamsch
Copy link
Copy Markdown
Contributor

@spamsch spamsch commented May 20, 2026

Closes #107.

What's in this PR

Terminal as a first-class feature

  • Removes the Developer Mode gate — New Terminal appears in every pane's + menu and command palette for all users
  • Consolidates terminal code from features/devtools/terminal/ into features/terminal/
  • Full 16-colour ANSI palette wired to NeverWrite's theme system — all 20 themes have hand-tuned palettes applied synchronously on theme change
  • Font family and size configurable in Settings → Terminal, with document.fonts.load() pre-check for correct xterm.js cell metrics
  • COLORTERM=truecolor and extra_env passthrough added to the Rust PTY sidecar

Claude Code as a built-in agent provider

  • Claude Code CLI appears in AI Providers settings when detected in PATH (login-shell check via devtools_check_binary)
  • Auto-selected as the default new-chat provider when found and no explicit preference is set; selection persists across restarts
  • "Add to chat" routes to a terminal session instead of a chat tab when Claude Code is the default: opens terminal, cds to vault or selected folder, starts claude with configured flags, pre-fills @mentions for attached notes and files
  • Settings → Terminal → Claude Code section (only shown when binary found): skip permissions (--dangerously-skip-permissions), model selection, continue last session (--continue), max turns (--max-turns)
  • AI Providers page shows a note directing users to Terminal settings for Claude Code configuration

Simon Pamies added 3 commits May 20, 2026 12:25
…ures/terminal/

Move TerminalViewport, terminalTheme, terminalTypes, terminalRawOutput,
terminalSessionTracking, and useTerminalTabs out of features/devtools/terminal/
into features/terminal/ alongside the existing runtime store and host components.

Update all imports, rename the CSS class from .devtools-terminal-surface to
.terminal-surface, and expand terminalTheme to include the full 16-colour ANSI
palette (mapped from the app's Catppuccin icon tokens) and font-options support.

Includes the implementation plan in docs/terminal-integration.md.
Ungate the terminal from Developer Mode — New Terminal appears in every pane's
+ menu and the command palette without requiring developerModeEnabled.

Settings → Terminal section: font family (with document.fonts.load() pre-check
for correct xterm.js cell metrics), font size, and Claude Code rendering toggle
(CLAUDE_CODE_NO_FLICKER=1). Right-panel layout watcher re-fits the terminal
after peek overlays open/close.

Rust sidecar: COLORTERM=truecolor added as default; extra_env passthrough for
per-session env overrides; devtools_check_binary command for PATH-aware binary
detection via login shell.

TypeScript: extraEnv field threads through TerminalSessionCreateInput and both
terminal session create call sites.
Registers claude-code-terminal as a pseudo-runtime injected alongside ACP
providers in chatStore. Binary presence is detected via devtools_check_binary
(login shell) at initialization; result drives binaryReady/authReady and the
auto-default selection (Claude Code becomes the default new-chat target when
found and no explicit preference is set).

selectedRuntimeId persistence: saved to AI preferences so the choice survives
restarts. getDefaultNewChatRuntimeId() reads prefs + binary status directly,
bypassing the active-session override that would otherwise shadow the setting.

Add to chat routing: handleAttachToNewChat checks getDefaultNewChatRuntimeId()
and calls openClaudeCodeTerminalWithContext() when Claude Code is the target.
Context (notes, files) is formatted as quoted @mentions; a cd command scopes
the session to the vault root or the selected folder.

AI Providers settings: Claude Code appears in the INSTALLED section with a
"Ready" badge when found, and in ALL with an Install button (opens claude.ai/code)
when not. A "Default agent" selector with documentation sits above the
installed list. A note under the Claude Code row links to Terminal settings
for model, skip-permissions, max-turns, and continue-session configuration.
@spamsch spamsch force-pushed the feature/terminal-first-class branch 2 times, most recently from 7a86be0 to c01b55c Compare May 20, 2026 10:29
Adds terminalPalettes.ts: hand-tuned 16-colour ANSI palettes for all 20
NeverWrite themes (catppuccin, nord, solarized, gruvbox, tokyoNight, rosePine,
kanagawa, everforest, ayu, nightOwl, vesper, synthwave84, and the app-specific
default, ocean, forest, rose, amber, lavender, sunset, claude, codex).

Palettes are applied as --terminal-ansi-* CSS custom properties synchronously
inside applyThemeColors() — same call site, same tick, no effect-ordering
issue. getTerminalTheme() reads --terminal-ansi-* first, falling back to the
existing --catppuccin-icon-* tokens for any theme without a custom entry.

When the user switches themes, the terminal colours update live via the
existing useThemeStore subscription in TerminalViewport.
@spamsch spamsch force-pushed the feature/terminal-first-class branch from c01b55c to 2f9452a Compare May 20, 2026 10:39
@spamsch spamsch marked this pull request as draft May 20, 2026 10:41
Simon Pamies and others added 5 commits May 20, 2026 12:52
Subscription-based auth (claude-ai-login, claude-login, console-login) only
works with the Claude Code CLI, not the ACP sidecar. Strip these methods from
claude-acp in normalizeRuntimeSetupStatus() and mark the runtime as not-ready
when the current auth is subscription-based.

Adds a note in the Claude provider expanded panel directing users to configure
an Anthropic API key. If only a subscription was previously configured, Claude
ACP now shows as "Not configured" until a key is added.
Security:
- devtools_check_binary: validate binary name against [A-Za-z0-9._-] before
  interpolating into sh -lc to prevent shell injection
- cd command: switch from double-quote to single-quote escaping so $, backticks,
  and backslash in vault/folder names can't execute arbitrary shell code

Bugs:
- chatPaneMovement: remove the second guard clause (default-runtime check) that
  was silently returning null for explicit ACP runtime requests (e.g. clicking
  + Codex from the sidebar) when Claude Code was the default provider
- ai:new-agent command/shortcut: route to openClaudeCodeTerminalWithContext()
  when Claude Code is the default instead of calling createNewChatInWorkspace()
  which would silently no-op

Correctness:
- buildContextArgs: strip vault root prefix from absolute note/file paths so
  @mentions are vault-relative rather than exposing full filesystem paths
- @mention quoting: use safe-character whitelist instead of space-only check
- Thread paneId through openClaudeCodeTerminalWithContext so terminals opened
  from a specific pane's + menu land in the right pane
De-duplicate descriptor (jsgrrchg#2):
Extract CLAUDE_TERMINAL_DESCRIPTOR and buildClaudeTerminalSetupStatus into
features/ai/utils/claudeTerminalRuntime.ts. chatStore and AIProvidersSettings
now share one definition with consistent copy.

Subscribe-based terminal ready (jsgrrchg#3):
Replace setInterval+setTimeout polling in waitForTerminalRunning with a
synchronous pre-check followed by useTerminalRuntimeStore.subscribe. No busy
loop, no 100ms lag, logs a warning on timeout.

Raise settle delay (jsgrrchg#4):
CLAUDE_TUI_SETTLE_MS 2000 → 3500 with a comment explaining the limitation
and what a proper fix would require.

Binary check cache (#1):
Module-level cache in checkClaudeCodeInstalled() so chatStore, TerminalSettings,
and AIProvidersSettings share one sh spawn rather than three.

Persist auto-selection (jsgrrchg#5):
When Claude Code is auto-selected on first launch (binary found, no prior
preference), persist it to AiPreferences so binary removal/reinstall doesn't
silently change the default on next start. ACP runtime auto-selection is NOT
persisted — only the Claude Code terminal selection is.
@jsgrrchg
Copy link
Copy Markdown
Owner

Hey I did a quick fix to connect the behavior in the sidebar to the new chat behavior and the cmd+shift+n shortcut.

Also I found this issues, if you are busy let me know, I can take it from here, I'll add now a few tests

  1. The PR still does not typecheck. npx tsc -b --pretty false fails on:

    • UnifiedBar.tsx: still passes developerModeEnabled/developerTerminalEnabled to buildNewTabContextMenuEntries, but that signature no longer accepts them.
    • TerminalViewport.tsx: terminal.textarea can be undefined but is assigned to HTMLTextAreaElement | null.
    • EditorPaneContent.test.tsx / terminalRuntimeStore.test.ts: imports still point to the removed ../devtools/terminal/terminalTypes path.
  2. The latest commit only partially addresses the Claude Code pseudo-runtime issue. It correctly routes the Agents sidebar menu item to openClaudeCodeTerminalWithContext(), but createNewChatInWorkspace() can still resolve CLAUDE_TERMINAL_RUNTIME_ID internally after the initial guard and attempt to create a pending ACP chat session for a pseudo-runtime.

  3. The “Enable Integrated Terminal” setting is now inconsistent. Terminal can still be opened from first-class workspace actions even when the setting appears to disable it. We should either remove the setting if terminal is now first-class, or centralize a canOpenTerminal() gate and use it everywhere.

  4. Claude Code can still become the default runtime automatically when claude is found in PATH. That means “New Agent” style flows can open a raw terminal and bypass the app’s action log, inline review, agent changes panel, and accept/reject flow. This should probably require explicit opt-in and clear warning unless external change tracking is integrated.

  5. claude launch flags are still assembled as a shell string. claudeCodeModel is persisted as an arbitrary string, so a corrupted/manually edited setting could inject shell input. We should validate against an allowlist and/or shell-quote each argument before writing to the PTY.

Let me know your thoughts, and thank you for your work

@jsgrrchg
Copy link
Copy Markdown
Owner

Before merge, I think we should settle four remaining points:

  1. createNewChatInWorkspace() can still internally resolve CLAUDE_TERMINAL_RUNTIME_ID; Claude Code should never enter the ACP chat-session path.
  2. “Enable Integrated Terminal” is now inconsistent with terminal-first-class entrypoints. We should either remove it or centralize a gate.
  3. Auto-defaulting to Claude Code when claude is found in PATH may be too implicit, since it bypasses action log/review/change controls.
  4. Claude launch flags are assembled as a shell string from persisted settings; model/args should be validated or quoted.

Some of this may be intentional product direction, 2 and 3 gives me that impression.

@jsgrrchg jsgrrchg marked this pull request as ready for review May 21, 2026 15:26
@jsgrrchg jsgrrchg merged commit 3358829 into jsgrrchg:main May 21, 2026
8 checks passed
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.

Terminal: promote to first-class workspace feature (settings, theming, font)

2 participants