feat: BooCode 2.0 UI — Ember theme, brand banner, coder tabs, model-attribution chips

- Ember theme (Obsidian charcoal + #ff7a18 orange), now DEFAULT_THEME_ID; server theme_id whitelist gains 'ember'
- Brand banner: transparent Westie mascot + >_BooCode wordmark, big/edge-to-edge (flood-filled to transparency + cropped)
- Coder panes are multi-tab: + opens a BooCode tab, split opens a pane (shared ChatTabBar via tabKind + createCoderTab; closeOtherTabs/tab-numbering extended to coder)
- Model-attribution: new messages.model column stamped at finalizeCompletion (BooChat/native coder) + dispatcher assistant-row creation (external coder); surfaced via view + wire types + live frame; rendered as a subtle shortened-name chip (shortenModelName)
- Composer Web toggle moved into a boxed focus-ringed input; glowing accent dot on tool rows
- Claude SDK follow-ups (1M context, follow-up-message fix, collapsed thinking/tool chips) + CLAUDE_SDK_BACKEND=1

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 22:30:47 +00:00
parent 7098014261
commit 3a646fd6df
34 changed files with 482 additions and 163 deletions

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { Terminal, Code, Clipboard } from 'lucide-react';
import { Terminal, Clipboard } from 'lucide-react';
import { api } from '@/api/client';
import type { Chat, Project, Session, WorkspacePane } from '@/api/types';
import { MAX_PANES, activePaneChatId, type UseWorkspacePanesResult } from '@/hooks/useWorkspacePanes';
@@ -60,6 +60,7 @@ export function Workspace({
closeAllTabs,
showLandingPage,
addSplitPane,
createCoderTab,
removePane,
reopenPane,
hasClosedPanes,
@@ -214,18 +215,27 @@ export function Workspace({
onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
/>
)}
{/* Coder panes host BooCode tabs (one chat = one agent context,
all sharing the session worktree). "+" adds a tab; the split
button adds a pane. Same tab strip as chat panes (tabKind). */}
{isCoder && !isMobile && (
<div className="flex items-center gap-1 border-b border-border px-2 py-1 shrink-0">
<Code size={12} className="text-muted-foreground" />
<span className="text-xs text-muted-foreground">BooCode</span>
<PaneHeaderActions
className="ml-auto"
onSplitPane={onAddPane}
onReopenPane={hasClosedPanes ? reopenPane : undefined}
onShowHistory={() => showLandingPage(idx)}
onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
/>
</div>
<ChatTabBar
pane={pane}
tabs={chatsForPane(pane)}
tabKind="coder"
tabNumbers={tabNumbers}
onSwitchTab={(tabIdx) => switchTab(idx, tabIdx)}
onRemoveTab={(chatId) => removeTab(idx, chatId)}
onCloseOthers={(chatId) => closeOtherTabs(idx, chatId)}
onCloseToRight={(chatId) => closeTabsToRight(idx, chatId)}
onCloseAll={() => closeAllTabs(idx)}
onNewTab={() => void createCoderTab(idx)}
onSplitPane={(kind) => onAddPane(kind)}
onReopenPane={hasClosedPanes ? reopenPane : undefined}
onShowHistory={() => showLandingPage(idx)}
onRename={renameChat}
onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
/>
)}
{isTerminal && (
<div className="flex items-center gap-2 border-b border-border bg-muted/30 px-2 py-1 shrink-0">