diff --git a/.gitignore b/.gitignore index 614945b..0d0df28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Local agent/runtime state .omc/ .omx/ +.superpowers/ # macOS / OS metadata .DS_Store diff --git a/DESIGN.md b/DESIGN.md index e21f7a9..63500dc 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -2,7 +2,7 @@ ## Source of truth - Status: Active -- Last refreshed: 2026-06-21 +- Last refreshed: 2026-07-23 - Primary product surfaces: Workbranch Companion macOS menu bar popover. - Evidence reviewed: - `docs/plans/0032-companion-tauri-react-rewrite.md` @@ -12,11 +12,13 @@ - `apps/companion/src/App.tsx` - `apps/companion/src/ui/TaskRow.tsx` - `apps/companion/src/style.css` + - `https://brainless.swerdlow.dev/components` + - Brainless `claude-header`, `claude-message`, `codex-header`, and `codex-message` registry sources reviewed on 2026-07-20 ## Brand - Personality: fast, focused, terminal-native, command-line HUD. - Trust signals: fast refresh, clear current step, visible repo dirty/branch state, fixed-width readability, restrained terminal accents. -- Avoid: marketing hero layouts, oversized cards, large SaaS rows, generic dashboard cards, decorative animation, glossy neon chrome. Tasteful elevation and a sans/mono type hierarchy are welcome. +- Avoid: marketing hero layouts, oversized cards, large SaaS rows, generic dashboard cards, decorative animation, glossy neon chrome, soft-elevation card stacks, and mixed UI typography that weakens the terminal identity. ## Product goals - Goals: @@ -24,6 +26,7 @@ - Make the current plan step feel like the selected command/result in a launcher. - Keep repo branch/dirty state visible without competing with task progress. - Keep actions discoverable but visually secondary. + - Let the user choose a Claude Code or Codex CLI experience that applies to Main, Activity, and Settings. - Non-goals: - Task lifecycle mutation UI. - Full activity report implementation. @@ -43,15 +46,15 @@ - Key contexts of use: quick menu bar glance while coding, before switching tasks, during AI-agent execution. ## Information architecture -- Primary navigation: sticky floating bottom view bar with three destinations: Main, Activity, Setting. -- Core screens: Main task list, Activity report stub, Setting preferences view. +- Primary navigation: an inset floating terminal tab bar anchored to the viewport bottom with three destinations: Main, Activity, Settings. +- Core screens: Main task list, Activity report, Settings preferences view. - Content hierarchy: - 1. Global inventory + status rollup and icon-only refresh. + 1. Compact global inventory (`projects · tasks`) and icon-only refresh. 2. Active view content. 3. Main view: project group header, task name/status/progress/notification, repo branch/dirty state, active plan/current step, actions. - 4. Activity view: future report stub only in this slice. - 5. Setting view: launch-at-login, font, and theme controls. - 6. Screen-reader live status in the top toolbar and bottom view navigation; routine `Updated`/`Ready` text stays out of the visible top line. + 4. Activity view: existing day/three-day calendar, session selection, and reload behavior inside the agent shell. + 5. Settings view: launch-at-login, font, and agent theme controls. + 6. Screen-reader live status in the agent shell; routine `Updated`/`Ready` text stays out of the visible header. ## Design principles - Principle 1: Status is a launcher signal, not a paragraph. Use compact dots, counts, and labels. @@ -60,42 +63,48 @@ - Tradeoffs: density is preferred over spaciousness, but tap/click targets remain at least 32px high where practical. ## Visual language -- Color: Settings exposes one curated companion palette rather than parallel color families. Dark mode resolves to Catppuccin-inspired surfaces (`#1e1e2e`, `#181825`) with lavender/pink accents (`#cba6f7`, `#f5c2e7`) because it is the preferred dark look. Light mode is white/neutral rather than yellow: near-white surfaces (`#ffffff`, `#fbfcff`, `#f4f6fb`) with dark slate text (`#242633`) and a subtle lavender accent (`#7c3aed`). The visible setting is only `Light | Dark | System`; legacy theme families and concrete theme values collapse to this Companion palette while preserving mode. -- Typography: dual-axis. UI chrome (task/project names, headings, button and nav labels, settings controls) uses a system sans stack for hierarchy and readability; developer data (repo/branch/path, counts, plan label) stays monospace. The user-selectable font preference controls the monospace data stack. -- Spacing/layout rhythm: compact terminal/preferences rhythm, 8px grid, row-first grouping, prompt-like separators, subtle grid or border lines, no large cards. -- Shape/radius/elevation: one fixed radius scale (`6px` controls, `10px` rows, `14px` shell); depth from clearly stepped tonal surfaces, soft elevation shadows on cards, and hairline borders; cards lift on hover. Avoid heavy gradients and glossy neon glow. +- Color: Settings exposes two fixed-dark agent themes. Claude Code uses `#cd694a` only for small identity and interaction signals such as prompt markers, active underlines, focus rings, and narrow selected-state boundaries. Structural borders and broad selected/current surfaces use the same neutral graphite family as Codex, while `#c0caf5` remains the primary terminal text and `#949494` remains muted text. Codex uses `#ededed` for primary text, `#949494` for secondary text, `#3a3a3a` for borders, and `#5cc2e0` only for command-like actions and links. Existing Companion, Light, Dark, System, and legacy family values migrate to Claude Code. +- Typography: the agent shell, navigation, content panels, form controls, task metadata, and activity labels use the user-selected monospace stack. Weight, contrast, spacing, and rules create hierarchy instead of a sans/mono split. +- Spacing/layout rhythm: compact terminal rhythm, 8px grid, row-first grouping, prompt markers, and thin rules. Main, Activity, and Settings use the same expanded agent header so switching tabs does not shift the content vertically. +- Shape/radius/elevation: agent headers use a restrained `6px` radius from the Brainless reference. Panels and rows use square or near-square corners, flat tonal separation, and hairline borders. The bottom navigation and progress count may use pill radii, and the floating navigation may use one restrained shadow to separate it from scrolling content. Do not use card hover lift, gradient, or decorative shadows elsewhere. - Motion: 120ms press/reveal feedback only; respect reduced motion. - Imagery/iconography: status state uses quiet color dots with accessible labels; avoid status checkmark glyphs in task headers. Checklist rows use a separate aligned marker column instead of checkmark or checkbox text prefixes; depth 0 uses a small quiet square marker, depth 1+ keeps circular markers, and completed markers use neutral muted tones rather than loud green. No decorative illustration. ## Components -- Existing components to reuse: `TaskRow`, action buttons, native `details/summary` disclosure. +- Existing components to reuse: `TaskRow`, action buttons, native `details/summary` disclosure, activity calendar behavior, and settings preference controls. - New/changed components: - - global inventory/status summary, + - shared `AgentShell` that applies `claude` or `codex` to all views, + - shared expanded `AgentHeader` for Main, Activity, and Settings with one theme-neutral text anatomy, + - `AgentTabs` as an inset floating bottom terminal navigation, + - shared `TerminalPanel`, `PromptLine`, and `StatusToken` primitives, + - compact global inventory summary limited to project and task counts, - project group header, - top toolbar with icon-only refresh/quit controls and screen-reader-only live status, - - sticky floating bottom view navigation for Main, Activity, and Setting, - - Setting view preferences panel, - - Activity report stub view, + - Settings view preferences panel, + - Settings preference sections always use the Claude Code `fieldset`/`legend` anatomy in both themes; the selected theme still owns colors and control state, + - Activity report view, - switch row for launch-at-login, - font select row, - - theme mode segmented control (`Light`, `Dark`, `System`) with no visible color-family grid, - - launcher-like task summary line, + - agent theme segmented control (`Claude Code`, `Codex`) with Claude Code as the default and migration target, + - launcher-like task summary line with a compact progress pill, - current-step strip, - detail header row containing repo chips and a full-width IDE/Terminal/Finder action bar split into equal thirds above current step and checklist content, - nested step tree with aligned status markers separate from step text: small depth 0 square, depth 1 circle, deeper levels smaller circles. -- Variants and states: todo, planning, in-progress, review, blocked, done, notification present, dirty repo. +- Variants and states: todo, planning, in-progress, review, blocked, done, notification present, dirty repo. In-progress identity uses the compact status marker rather than recoloring the full Task perimeter. +- Selected Task: the expanded native `details[open]` row uses a stronger neutral surface and border while collapsed tasks remain quiet. Its summary uses the theme-owned `--task-selected-summary-bg` low-opacity tint plus a `2px` inline-start accent boundary; the accent must not fill the entire summary row at full strength. +- Shared header anatomy: Claude Code and Codex use the same text-only title block: `Workbranch Companion` above `projects · tasks`. The top banner contains no Workbranch mark, product icon, or Claude/Codex prompt prefix; theme identity comes from surrounding color tokens rather than different header geometry. Task/current-step rows may retain their theme-specific prompt and action accents. - Token/component ownership: `style.css` is the CSS import manifest; `src/styles/base.css`, `themes.css`, `chrome.css`, `task-details.css`, `status-groups.css`, `settings.css`, and `motion.css` own CSS custom properties and component classes by surface. ## Accessibility - Target standard: keyboard-operable popover controls and readable contrast. - Keyboard/focus behavior: buttons and `summary` expose clear focus rings. - Contrast/readability: status text and current step must pass practical dark-mode contrast; disabled action may be muted but legible. -- Screen-reader semantics: preserve button `aria-label`s; bottom view buttons expose destination labels and `aria-current`; settings controls use associated labels, switch state text, and the toolbar keeps a screen-reader-only `role="status"` region with polite live updates. +- Screen-reader semantics: preserve button `aria-label`s; agent tab buttons expose destination labels and `aria-current`; settings controls use associated labels, switch state text, and the agent shell keeps a screen-reader-only `role="status"` region with polite live updates. - Reduced motion and sensory considerations: disable transform transitions under `prefers-reduced-motion: reduce`. ## Responsive behavior - Supported breakpoints/devices: narrow menu popover around 360-460px width. -- Layout adaptations: the detail header keeps repo chips above a full-width launch-action row; IDE, Terminal, and Finder each occupy one third of that row above steps; repo chips wrap; long task/step names truncate with accessible full text via `title`. +- Layout adaptations: the shared expanded header collapses its internal columns consistently on every tab. The detail header keeps repo chips above a full-width launch-action row; IDE, Terminal, and Finder each occupy one-third of that row above steps; repo chips wrap; long task/step names truncate with accessible full text via `title`. - Touch/hover differences: hover is enhancement only; core state is visible without hover. ## Interaction states @@ -104,6 +113,7 @@ - Error: root-scoped error row with red accent; operation failures such as refresh/action/preference errors also render a visible alert row while routine Ready/Updated statuses stay screen-reader-only. - Success: routine `Updated` / `Action complete` messages are not shown as a visible top-line chip; they remain available to assistive tech. - Disabled: disabled action has muted text and no press transform. +- Theme selection: Settings is the only visible theme switch surface. Selection applies to every view immediately and persists through the existing preference store. Unsupported and legacy theme values migrate to Claude Code. - Offline/slow network: not applicable; CLI/local filesystem driven. ## Content voice @@ -112,14 +122,15 @@ - Microcopy rules: prefer short row action labels (`IDE`, `Terminal`, `Finder`) over sentences; omit `Copy`/`Memo`/`Noti`/`Clear` row vocabulary because those companion actions are removed, not hidden. ## Implementation constraints -- Framework/styling system: React 18 + plain CSS; no Tailwind/shadcn in this refresh. Primary reference is a modern developer HUD with a terminal core (mono data) and a modern shell (sans chrome, stepped surfaces, soft elevation); Raycast remains a secondary compact-popover cue. +- Framework/styling system: React 18 + plain CSS. Adapt the structure and accessibility behavior of the Brainless Claude and Codex components into local reusable primitives. Do not add Tailwind, shadcn, or a new runtime package. - Design-token constraints: CSS custom properties in `style.css`. - Performance constraints: no extra runtime package; no animation loops; preserve 0033 responsiveness fixes. - Compatibility constraints: no CLI/contract/Rust port changes. -- Test/screenshot expectations: Vitest static markup tests plus `pnpm --filter @workbranch/companion tauri build`; run a browser/app screenshot review before final handoff. +- Scope constraints: do not add keyboard shortcuts or display shortcut hints for behavior that does not exist. +- Test/screenshot expectations: cover preference migration, persistence, both theme variants, and the Main/Activity/Settings shell contracts with Vitest. Run typecheck, lint, Vite build, Tauri build, then inspect both themes on all three tabs at 360px and 460px before final handoff. ## Open questions -- [ ] Whether a later v2 should add shadcn/Radix primitives after the plain CSS refresh proves the needed component gaps. +- [ ] Whether a later release should restore a light appearance as a separate axis after the two fixed-dark agent themes ship. ## Direction revision @@ -134,3 +145,12 @@ - 2026-06-22 (toolbar): Removed the visible top toolbar status chip after review; the top line now keeps task inventory plus icon controls only. A screen-reader-only polite live region preserves status announcements without showing routine `Updated` text in the popover chrome. - 2026-06-23: Light mode shifted from yellow/warm Breakfast parchment to a whiter neutral palette with subtle lavender accents after visual review. +- 2026-07-20: Replaced the Companion `Light | Dark | System` direction with two fixed-dark agent themes: Claude Code and Codex. Both themes apply to Main, Activity, and Settings and change component anatomy as well as tokens. The app uses an Agent Shell with terminal tabs and local React/plain-CSS primitives adapted from the Brainless component semantics. Claude Code is the default and migration target. Existing behavior and Tauri/Rust contracts remain unchanged. +- 2026-07-23: Removed the visible `Claude Code` legend from the expanded Main header so neither theme displays an agent product name as a banner label. The Settings theme-picker labels remain unchanged. +- 2026-07-23 (tab stability): Main, Activity, and Settings now share the same expanded `AgentHeader` anatomy and size. The compact secondary-view bar was removed so tab changes preserve a stable top-banner footprint. +- 2026-07-23 (floating navigation): Moved `AgentTabs` from below the header to an inset floating bottom bar while preserving the terminal theme. Expanded tasks now use selected-row background emphasis and progress is shown in a compact pill. +- 2026-07-23 (selected Task tone): Reduced the expanded Task summary from the broad `--emphasis-soft` fill to a theme-owned low-opacity surface tint with a narrow `2px` inline-start accent. The selected state remains visible through the boundary, prompt marker, border, and progress pill without creating a wide saturated band. +- 2026-07-23 (Claude accent restraint): Replaced Claude's orange structural borders and broad selected/current fills with Codex-like graphite borders and cool neutral translucent surfaces. Claude orange remains only on compact identity, focus, prompt, and active-state signals. +- 2026-07-23 (header inventory): Limited both theme headers to `projects · tasks`, removed Codex-only model/directory metadata, and aligned the Claude/Codex banner footprint. +- 2026-07-23 (text-only header): Removed the Workbranch mark and Claude/Codex prompt prefixes from the top banner. Both themes now share the exact `Workbranch Companion` plus `projects · tasks` text structure and header geometry. +- 2026-07-23 (Settings anatomy): Standardized Startup, Font, and Theme sections on the Claude Code `fieldset`/`legend` structure in both themes while preserving each theme's color tokens and selected state. diff --git a/apps/companion/src/App.tsx b/apps/companion/src/App.tsx index 5b5bf93..71ebcf8 100644 --- a/apps/companion/src/App.tsx +++ b/apps/companion/src/App.tsx @@ -2,9 +2,7 @@ import { isTauri } from "@tauri-apps/api/core"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ActivityCalendarView } from "./activity/ActivityCalendarView"; import { createActivityRefresh } from "./application/activity"; -import { resolvedCompanionTheme } from "./application/preferences"; import { buildMenuModel, type MenuModel } from "./application/state"; -import { useSystemThemeMode } from "./application/systemThemeMode"; import { useCompanionSettings } from "./application/useCompanionSettings"; import type { GlobalState, Task } from "./domain/model"; import { @@ -18,13 +16,12 @@ import { watchRoots, } from "./infrastructure/tauriClient"; import { startWorkspaceMonitor } from "./infrastructure/workspaceMonitor"; -import { AppSummary } from "./ui/AppSummary"; -import { AppToolbar } from "./ui/AppToolbar"; +import { AgentHeader } from "./ui/AgentHeader"; +import { AgentTabs, type CompanionView } from "./ui/AgentTabs"; import { ProjectGroup } from "./ui/ProjectGroup"; import { SettingsView } from "./ui/SettingsView"; import { StatusAlert } from "./ui/StatusAlert"; import type { TaskActionKind } from "./ui/TaskRow"; -import { type CompanionView, ViewNav } from "./ui/ViewNav"; const EMPTY_STATE: GlobalState = { projects: [], errors: [] }; const TAURI_RUNTIME_UNAVAILABLE = "Tauri runtime unavailable"; @@ -57,7 +54,6 @@ export function App() { const [status, setStatus] = useState("Ready"); const [visibleError, setVisibleError] = useState(); const [currentView, setCurrentView] = useState("main"); - const systemThemeMode = useSystemThemeMode(); const model: MenuModel = buildMenuModel(state); const tauriRuntimeAvailable = isTauri(); const refreshWithActivity = useMemo( @@ -92,7 +88,7 @@ export function App() { updateLaunchAtLogin, updatePreferences, } = useCompanionSettings({ onError: showError, onStatus: showStatus }); - const activeTheme = resolvedCompanionTheme(preferences, systemThemeMode); + const activeTheme = preferences.theme; const applyState = useCallback( (next: GlobalState) => { @@ -188,14 +184,13 @@ export function App() { return (
-
- - void refresh()} - onQuit={handleQuit} - status={status} - /> -
+ void refresh()} + onQuit={handleQuit} + /> {currentView === "main" ? (
@@ -206,6 +201,7 @@ export function App() { void handleTaskAction(root, task, kind) } @@ -228,7 +224,6 @@ export function App() { {currentView === "settings" ? ( void updateLaunchAtLogin(enabled)} @@ -240,7 +235,7 @@ export function App() { {error.root}: {error.message}

))} - +
); } diff --git a/apps/companion/src/activity/activity-calendar.css b/apps/companion/src/activity/activity-calendar.css index 71aafb5..7e3bd7b 100644 --- a/apps/companion/src/activity/activity-calendar.css +++ b/apps/companion/src/activity/activity-calendar.css @@ -2,7 +2,7 @@ align-items: stretch; background: var(--surface-1); border: 1px solid var(--line); - border-radius: var(--radius-row); + border-radius: 6px; display: block; padding: 10px; text-align: left; @@ -82,37 +82,37 @@ .cal-chip[data-color="0"], .cal-session[data-color="0"] { - --cal-bg: rgba(203, 166, 247, 0.18); + --cal-bg: var(--cal-bg-1); --cal-color: var(--cal-1); } .cal-chip[data-color="1"], .cal-session[data-color="1"] { - --cal-bg: rgba(137, 180, 250, 0.18); + --cal-bg: var(--cal-bg-2); --cal-color: var(--cal-2); } .cal-chip[data-color="2"], .cal-session[data-color="2"] { - --cal-bg: rgba(166, 227, 161, 0.18); + --cal-bg: var(--cal-bg-3); --cal-color: var(--cal-3); } .cal-chip[data-color="3"], .cal-session[data-color="3"] { - --cal-bg: rgba(249, 226, 175, 0.18); + --cal-bg: var(--cal-bg-4); --cal-color: var(--cal-4); } .cal-chip[data-color="4"], .cal-session[data-color="4"] { - --cal-bg: rgba(245, 194, 231, 0.18); + --cal-bg: var(--cal-bg-5); --cal-color: var(--cal-5); } .cal-chip[data-color="5"], .cal-session[data-color="5"] { - --cal-bg: rgba(148, 226, 213, 0.18); + --cal-bg: var(--cal-bg-6); --cal-color: var(--cal-6); } @@ -134,7 +134,7 @@ } .cal-hour-label { - color: var(--faint); + color: var(--muted); font-size: 10px; line-height: 1; padding-top: 1px; @@ -150,7 +150,7 @@ .cal-day-column { background: var(--surface-2); border: 1px solid var(--line); - border-radius: 10px; + border-radius: 4px; min-width: 0; overflow: hidden; position: relative; @@ -188,8 +188,7 @@ background: var(--cal-bg); border-color: var(--cal-color, var(--accent)); border-left: 3px solid var(--cal-color, var(--accent)); - border-radius: 7px; - box-shadow: none; + border-radius: 4px; color: var(--text); display: grid; gap: 1px; @@ -217,6 +216,10 @@ background: var(--cal-bg); } +.cal-session[data-selected="true"] { + border-color: var(--emphasis); +} + @supports (background: color-mix(in srgb, black, white)) { .cal-session { background: color-mix( @@ -282,7 +285,7 @@ .cal-detail { background: var(--surface-2); border: 1px solid var(--line); - border-radius: 10px; + border-radius: 4px; display: grid; gap: 5px; padding: 8px; diff --git a/apps/companion/src/application/preferences.ts b/apps/companion/src/application/preferences.ts index e4a4222..2f485c8 100644 --- a/apps/companion/src/application/preferences.ts +++ b/apps/companion/src/application/preferences.ts @@ -1,32 +1,15 @@ import { load } from "@tauri-apps/plugin-store"; import { - COMPANION_THEME_MODE_OPTIONS, COMPANION_THEME_OPTIONS, - type CompanionResolvedThemeMode, type CompanionTheme, - type CompanionThemeFamily, - type CompanionThemeMode, - isCompanionThemeFamily, - isCompanionThemeMode, - isLegacyCompanionTheme, - resolvedThemeValue, - themeFamilyFromLegacyTheme, - themeModeFromLegacyTheme, + isCompanionTheme, } from "./themePreferences"; export type { - CompanionResolvedThemeMode, CompanionTheme, - CompanionThemeFamily, - CompanionThemeMode, - CompanionThemeModeOption, CompanionThemeOption, } from "./themePreferences"; -export { - COMPANION_THEME_MODE_OPTIONS, - COMPANION_THEME_OPTIONS, - resolvedThemeValue, -}; +export { COMPANION_THEME_OPTIONS }; export const COMPANION_PREFERENCES_STORE_FILE = "companion-preferences.json"; @@ -42,8 +25,7 @@ export type CompanionFont = (typeof COMPANION_FONT_VALUES)[number]; export type CompanionPreferences = { readonly font: CompanionFont; - readonly themeFamily: CompanionThemeFamily; - readonly themeMode: CompanionThemeMode; + readonly theme: CompanionTheme; }; export type CompanionFontOption = { @@ -59,8 +41,7 @@ export type PreferenceSanitizationResult = { export type PreferenceStoreEntries = { readonly font: CompanionFont; - readonly themeFamily: CompanionThemeFamily; - readonly themeMode: CompanionThemeMode; + readonly theme: CompanionTheme; }; export type CompanionPreferenceStore = { @@ -71,8 +52,7 @@ export type CompanionPreferenceStore = { export const DEFAULT_COMPANION_PREFERENCES: CompanionPreferences = { font: "system-mono", - themeFamily: "companion", - themeMode: "system", + theme: "claude", }; export const COMPANION_FONT_OPTIONS: readonly CompanionFontOption[] = [ @@ -119,54 +99,23 @@ export function isCompanionFont(value: unknown): value is CompanionFont { } } -function migrateRemovedThemeFamily( - value: unknown, -): CompanionThemeFamily | undefined { - switch (value) { - case "breakfast": - case "solarized": - case "dracula": - case "catppuccin": - case "github": - case "gruvbox": - case "nord": - return "companion"; - default: - return undefined; - } -} - export function sanitizeCompanionPreferences(input: { readonly font?: unknown; + readonly theme?: unknown; readonly themeFamily?: unknown; readonly themeMode?: unknown; - readonly theme?: unknown; }): PreferenceSanitizationResult { const font = isCompanionFont(input.font) ? input.font : DEFAULT_COMPANION_PREFERENCES.font; - const legacyTheme = isLegacyCompanionTheme(input.theme) + const theme = isCompanionTheme(input.theme) ? input.theme - : undefined; - const removedThemeFamily = migrateRemovedThemeFamily(input.themeFamily); - const themeFamily = isCompanionThemeFamily(input.themeFamily) - ? input.themeFamily - : removedThemeFamily - ? removedThemeFamily - : legacyTheme - ? themeFamilyFromLegacyTheme(legacyTheme) - : DEFAULT_COMPANION_PREFERENCES.themeFamily; - const themeMode = isCompanionThemeMode(input.themeMode) - ? input.themeMode - : legacyTheme - ? themeModeFromLegacyTheme(legacyTheme) - : DEFAULT_COMPANION_PREFERENCES.themeMode; + : isCompanionTheme(input.themeFamily) + ? input.themeFamily + : DEFAULT_COMPANION_PREFERENCES.theme; return { - preferences: { font, themeFamily, themeMode }, - sanitized: - font !== input.font || - themeFamily !== input.themeFamily || - themeMode !== input.themeMode, + preferences: { font, theme }, + sanitized: font !== input.font || theme !== input.theme, }; } @@ -174,11 +123,7 @@ export function shouldRestoreFailedPreferenceUpdate( current: CompanionPreferences, attempted: CompanionPreferences, ): boolean { - return ( - current.font === attempted.font && - current.themeFamily === attempted.themeFamily && - current.themeMode === attempted.themeMode - ); + return current.font === attempted.font && current.theme === attempted.theme; } export type PreferenceSaveOperation = () => Promise; @@ -195,15 +140,14 @@ export function preferencesToStoreEntries( ): PreferenceStoreEntries { return { font: preferences.font, - themeFamily: preferences.themeFamily, - themeMode: preferences.themeMode, + theme: preferences.theme, }; } export async function loadCompanionPreferenceStore(): Promise { return load(COMPANION_PREFERENCES_STORE_FILE, { autoSave: false, - defaults: preferencesToStoreEntries(DEFAULT_COMPANION_PREFERENCES), + defaults: {}, }); } @@ -212,9 +156,9 @@ export async function readCompanionPreferences( ): Promise { return sanitizeCompanionPreferences({ font: await store.get("font"), + theme: await store.get("theme"), themeFamily: await store.get("themeFamily"), themeMode: await store.get("themeMode"), - theme: await store.get("theme"), }); } @@ -224,16 +168,6 @@ export async function writeCompanionPreferences( ): Promise { const entries = preferencesToStoreEntries(preferences); await store.set("font", entries.font); - await store.set("themeFamily", entries.themeFamily); - await store.set("themeMode", entries.themeMode); + await store.set("theme", entries.theme); await store.save(); } - -export function resolvedCompanionTheme( - preferences: CompanionPreferences, - systemMode: CompanionResolvedThemeMode, -): CompanionTheme { - const mode = - preferences.themeMode === "system" ? systemMode : preferences.themeMode; - return resolvedThemeValue(preferences.themeFamily, mode); -} diff --git a/apps/companion/src/application/systemThemeMode.ts b/apps/companion/src/application/systemThemeMode.ts deleted file mode 100644 index 798a8c6..0000000 --- a/apps/companion/src/application/systemThemeMode.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useState } from "react"; -import type { CompanionResolvedThemeMode } from "./themePreferences"; - -export function useSystemThemeMode(): CompanionResolvedThemeMode { - const [systemThemeMode, setSystemThemeMode] = - useState("dark"); - - useEffect(() => { - const query = window.matchMedia("(prefers-color-scheme: light)"); - const applySystemTheme = () => { - setSystemThemeMode(query.matches ? "light" : "dark"); - }; - applySystemTheme(); - query.addEventListener("change", applySystemTheme); - return () => { - query.removeEventListener("change", applySystemTheme); - }; - }, []); - - return systemThemeMode; -} diff --git a/apps/companion/src/application/themePreferences.ts b/apps/companion/src/application/themePreferences.ts index 939454f..e529132 100644 --- a/apps/companion/src/application/themePreferences.ts +++ b/apps/companion/src/application/themePreferences.ts @@ -1,155 +1,19 @@ -const COMPANION_THEME_VALUES = ["catppuccin-dark", "breakfast-light"] as const; - -const PREVIOUS_COMPANION_THEME_VALUES = [ - "terminal-dark", - "amber-crt", - "green-mono", - "high-contrast", - "gruvbox-dark", - "gruvbox-light", - "nord-dark", - "nord-light", - "breakfast-dark", - "solarized-dark", - "dracula-dark", - "github-dark", - "solarized-light", - "dracula-light", - "catppuccin-light", - "github-light", -] as const; - -const COMPANION_THEME_FAMILY_VALUES = ["companion"] as const; -const COMPANION_THEME_MODE_VALUES = ["light", "dark", "system"] as const; -const RESOLVED_THEME_MODE_VALUES = ["dark", "light"] as const; +const COMPANION_THEME_VALUES = ["claude", "codex"] as const; export type CompanionTheme = (typeof COMPANION_THEME_VALUES)[number]; -type PreviousCompanionTheme = (typeof PREVIOUS_COMPANION_THEME_VALUES)[number]; -type LegacyCompanionTheme = CompanionTheme | PreviousCompanionTheme; -export type CompanionThemeFamily = - (typeof COMPANION_THEME_FAMILY_VALUES)[number]; -export type CompanionThemeMode = (typeof COMPANION_THEME_MODE_VALUES)[number]; -export type CompanionResolvedThemeMode = - (typeof RESOLVED_THEME_MODE_VALUES)[number]; export type CompanionThemeOption = { readonly value: CompanionTheme; - readonly family: CompanionThemeFamily; - readonly familyLabel: string; readonly label: string; - readonly mode: CompanionResolvedThemeMode; -}; - -export type CompanionThemeModeOption = { - readonly value: CompanionThemeMode; - readonly label: string; -}; - -const COMPANION_THEME_SET = new Set(COMPANION_THEME_VALUES); -const PREVIOUS_COMPANION_THEME_SET = new Set( - PREVIOUS_COMPANION_THEME_VALUES, -); -const COMPANION_THEME_FAMILY_SET = new Set( - COMPANION_THEME_FAMILY_VALUES, -); -const COMPANION_THEME_MODE_SET = new Set(COMPANION_THEME_MODE_VALUES); - -const THEME_BY_MODE: Record = { - dark: "catppuccin-dark", - light: "breakfast-light", -}; - -const THEME_MODE_BY_LEGACY_THEME: Record< - LegacyCompanionTheme, - CompanionResolvedThemeMode -> = { - "terminal-dark": "dark", - "amber-crt": "dark", - "green-mono": "dark", - "high-contrast": "dark", - "gruvbox-dark": "dark", - "gruvbox-light": "light", - "nord-dark": "dark", - "nord-light": "light", - "breakfast-dark": "dark", - "breakfast-light": "light", - "solarized-dark": "dark", - "solarized-light": "light", - "dracula-dark": "dark", - "dracula-light": "light", - "catppuccin-dark": "dark", - "catppuccin-light": "light", - "github-dark": "dark", - "github-light": "light", }; export const COMPANION_THEME_OPTIONS: readonly CompanionThemeOption[] = [ - { - value: "catppuccin-dark", - family: "companion", - familyLabel: "Companion", - label: "Catppuccin Dark", - mode: "dark", - }, - { - value: "breakfast-light", - family: "companion", - familyLabel: "Companion", - label: "White Light", - mode: "light", - }, + { value: "claude", label: "Claude Code" }, + { value: "codex", label: "Codex" }, ]; -export const COMPANION_THEME_MODE_OPTIONS: readonly CompanionThemeModeOption[] = - [ - { value: "light", label: "Light" }, - { value: "dark", label: "Dark" }, - { value: "system", label: "System" }, - ]; +const COMPANION_THEME_SET = new Set(COMPANION_THEME_VALUES); export function isCompanionTheme(value: unknown): value is CompanionTheme { return COMPANION_THEME_SET.has(value); } - -function isPreviousCompanionTheme( - value: unknown, -): value is PreviousCompanionTheme { - return PREVIOUS_COMPANION_THEME_SET.has(value); -} - -export function isLegacyCompanionTheme( - value: unknown, -): value is LegacyCompanionTheme { - return isCompanionTheme(value) || isPreviousCompanionTheme(value); -} - -export function isCompanionThemeFamily( - value: unknown, -): value is CompanionThemeFamily { - return COMPANION_THEME_FAMILY_SET.has(value); -} - -export function isCompanionThemeMode( - value: unknown, -): value is CompanionThemeMode { - return COMPANION_THEME_MODE_SET.has(value); -} - -export function themeFamilyFromLegacyTheme( - _theme: LegacyCompanionTheme, -): CompanionThemeFamily { - return "companion"; -} - -export function themeModeFromLegacyTheme( - theme: LegacyCompanionTheme, -): CompanionResolvedThemeMode { - return THEME_MODE_BY_LEGACY_THEME[theme]; -} - -export function resolvedThemeValue( - _family: CompanionThemeFamily, - mode: CompanionResolvedThemeMode, -): CompanionTheme { - return THEME_BY_MODE[mode]; -} diff --git a/apps/companion/src/style.css b/apps/companion/src/style.css index 57ba082..e30fec3 100644 --- a/apps/companion/src/style.css +++ b/apps/companion/src/style.css @@ -1,6 +1,7 @@ @import "./styles/base.css"; @import "./styles/themes.css"; @import "./styles/chrome.css"; +@import "./styles/agent-shell.css"; @import "./styles/task-details.css"; @import "./styles/task-actions.css"; @import "./styles/status-groups.css"; diff --git a/apps/companion/src/styles/agent-shell.css b/apps/companion/src/styles/agent-shell.css new file mode 100644 index 0000000..bbd7811 --- /dev/null +++ b/apps/companion/src/styles/agent-shell.css @@ -0,0 +1,198 @@ +.agent-header { + background: var(--surface-1); + border: 1px solid var(--line-strong); + border-radius: 6px; + margin: 0 0 8px; + min-width: 0; +} + +.agent-header { + padding: 10px; +} + +.terminal-panel legend { + color: var(--emphasis); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; + padding: 0 5px; + text-transform: uppercase; +} + +.agent-header-row { + align-items: center; + display: flex; + gap: 10px; + justify-content: space-between; +} + +.agent-header-copy { + display: grid; + flex: 1 1 auto; + gap: 4px; + min-width: 0; +} + +.agent-header h1 { + color: var(--text); + font-size: 14px; + line-height: 1.25; +} + +.agent-inventory { + color: var(--muted); + font-size: 10px; +} + +.agent-controls { + align-items: center; + display: flex; + flex: 0 0 auto; + gap: 4px; + justify-content: flex-end; +} + +.agent-control { + align-items: center; + color: var(--muted); + display: inline-flex; + font-size: 15px; + justify-content: center; + min-width: 32px; + padding: 4px; +} + +.agent-control-quit:hover:not(:disabled) { + border-color: var(--blocked); + color: var(--blocked); +} + +.agent-tabs { + background: var(--surface-0); + border: 1px solid var(--line); + border-radius: var(--floating-tabs-radius); + bottom: calc(var(--floating-tabs-bottom) + env(safe-area-inset-bottom)); + box-shadow: var(--floating-tabs-shadow); + display: grid; + gap: 0; + grid-template-columns: repeat(3, minmax(0, 1fr)); + left: var(--floating-tabs-inset); + margin: 0; + overflow: hidden; + position: fixed; + right: var(--floating-tabs-inset); + z-index: 20; +} + +.agent-tab { + background: transparent; + border: 0; + border-bottom: 2px solid transparent; + border-radius: 0; + color: var(--muted); + font-size: var(--floating-tabs-font-size); + min-height: var(--floating-tabs-height); + min-width: 0; +} + +.agent-tab + .agent-tab { + border-left: 1px solid var(--line); +} + +.agent-tab[data-active="true"] { + background: var(--surface-2); + border-bottom-color: var(--emphasis); + color: var(--emphasis); + font-weight: 700; +} + +.terminal-panel { + background: var(--surface-1); + border: 1px solid var(--line); + border-radius: 4px; + margin: 0; + min-width: 0; + padding: 9px; +} + +.terminal-panel-heading { + border-bottom: 1px solid var(--line); + color: var(--emphasis); + font-size: 10px; + letter-spacing: 0.08em; + margin: -2px 0 8px; + padding-bottom: 6px; + text-transform: uppercase; +} + +.prompt-line { + align-items: baseline; + display: grid; + gap: 7px; + grid-template-columns: 10px minmax(0, 1fr); + min-width: 0; +} + +.prompt-line[data-current="true"] { + background: var(--current-step-bg); + border-left: 1px solid var(--emphasis); + padding: 5px 7px; +} + +.prompt-marker { + color: var(--emphasis); +} + +.prompt-content { + min-width: 0; + overflow-wrap: anywhere; +} + +.status-token { + align-items: center; + color: var(--muted); + display: inline-flex; + font-size: 10px; + gap: 5px; +} + +.status-token-marker { + background: var(--faint); + border-radius: 50%; + height: 6px; + width: 6px; +} + +.status-token[data-status="in-progress"] .status-token-marker { + background: var(--emphasis); +} + +.status-token[data-status="blocked"] .status-token-marker { + background: var(--blocked); +} + +.status-token[data-status="review"] .status-token-marker { + background: var(--review); +} + +.status-token[data-status="done"] .status-token-marker { + background: var(--done); +} + +@media (max-width: 400px) { + .agent-header-row { + align-items: center; + flex-wrap: nowrap; + } + + .agent-header h1 { + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .agent-inventory { + display: block; + } +} diff --git a/apps/companion/src/styles/base.css b/apps/companion/src/styles/base.css index 34d7b11..ab39a18 100644 --- a/apps/companion/src/styles/base.css +++ b/apps/companion/src/styles/base.css @@ -1,9 +1,9 @@ :root { - color: #faf6ef; - background: #15110d; + background: #0e0e0e; + color: #c0caf5; font-family: - -apple-system, "SF Pro Text", "Segoe UI", system-ui, "Helvetica Neue", - arial, sans-serif; + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + monospace; -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; } @@ -13,89 +13,39 @@ } body { + background: #0e0e0e; margin: 0; - min-width: 360px; - background: #15110d; -} - -body:has(main[data-theme$="-light"]) { - background: #ffffff; } main { - --ui-font-family: - -apple-system, "SF Pro Text", "Segoe UI", system-ui, "Helvetica Neue", - arial, sans-serif; --app-font-family: - ui-monospace, "SFMono-Regular", "Menlo", "Monaco", "Consolas", - "Liberation Mono", monospace; - --surface-0: #15110d; - --surface-1: #201913; - --surface-2: #2a2118; - --surface-3: #3a2b1e; - --line: rgba(255, 215, 154, 0.12); - --line-strong: rgba(255, 215, 154, 0.26); - --text: #faf6ef; - --muted: #b4aca2; - --faint: #8a837a; - --accent: #f59e0b; - --accent-strong: #ffd79a; - --accent-soft: rgba(245, 158, 11, 0.18); - --blocked: #e5484d; - --blocked-soft: rgba(229, 72, 77, 0.16); - --review: #c9c2ff; - --review-soft: rgba(201, 194, 255, 0.16); - --done: #1ca86a; - --done-soft: rgba(28, 168, 106, 0.16); - --notify: #ffc24b; - --notify-soft: rgba(255, 194, 75, 0.17); - --shell-bg: - radial-gradient(120% 120px at 50% 0, rgba(245, 158, 11, 0.13), transparent), - radial-gradient( - 90% 160px at 100% 12%, - rgba(255, 138, 30, 0.08), - transparent - ), - var(--surface-0); - --shell-topline: linear-gradient( - 90deg, - transparent, - var(--accent), - transparent - ); - --task-bg: var(--surface-2); - --task-bg-hover: var(--surface-3); - --button-bg: rgba(255, 215, 154, 0.07); - --button-bg-hover: rgba(255, 215, 154, 0.13); - --current-step-bg: - linear-gradient(135deg, var(--accent-soft), transparent 70%), - var(--surface-3); - --active-rail-bg: var(--accent-soft); - --shadow-soft: rgba(0, 0, 0, 0.32); - --shadow-strong: rgba(0, 0, 0, 0.52); - --shadow-card: - 0 1px 2px var(--shadow-soft), 0 8px 20px -10px var(--shadow-strong); - --shadow-card-hover: - 0 2px 4px var(--shadow-soft), 0 14px 28px -10px var(--shadow-strong); - --shadow-pop: 0 14px 36px var(--shadow-strong); - --radius-row: 11px; - --radius-shell: 14px; + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + monospace; + --floating-tabs-bottom: 10px; + --floating-tabs-content-clearance: 64px; + --floating-tabs-font-size: 11px; + --floating-tabs-height: 36px; + --floating-tabs-inset: 12px; + --floating-tabs-radius: 999px; + --floating-tabs-shadow: 0 8px 24px rgba(0, 0, 0, 0.46); + --progress-pill-font-size: 10px; + --progress-pill-padding: 3px 6px; + --progress-pill-radius: 999px; + --task-selected-accent-width: 2px; - position: relative; - max-width: 100%; - width: 100vw; - background: var(--shell-bg); + background: var(--surface-0); color: var(--text); display: flex; flex-direction: column; font-family: var(--app-font-family); + max-width: 100%; min-height: 100vh; padding: 12px; -} - -@supports (background: color-mix(in srgb, black, white)) { - main { - --button-bg: color-mix(in srgb, var(--text) 6%, transparent); - --button-bg-hover: color-mix(in srgb, var(--text) 12%, transparent); - } + /* biome-ignore format: Stylelint requires the continued operator at line start. */ + padding-bottom: calc( + var(--floating-tabs-content-clearance) + + env(safe-area-inset-bottom) + ); + position: relative; + width: 100vw; } diff --git a/apps/companion/src/styles/chrome.css b/apps/companion/src/styles/chrome.css index fd0016a..d982ec2 100644 --- a/apps/companion/src/styles/chrome.css +++ b/apps/companion/src/styles/chrome.css @@ -1,12 +1,3 @@ -header { - align-items: flex-start; - display: flex; - flex-wrap: wrap; - gap: 10px; - justify-content: space-between; - margin-bottom: 10px; -} - h1, h2, p { @@ -24,7 +15,7 @@ select { appearance: none; background: var(--button-bg); border: 1px solid var(--line); - border-radius: 7px; + border-radius: 6px; color: var(--text); min-height: 32px; padding: 6px 10px; @@ -40,27 +31,10 @@ button:focus-visible, select:focus-visible, input:focus-visible, summary:focus-visible { - outline: 2px solid var(--accent); + outline: 2px solid var(--emphasis); outline-offset: 2px; } -.toolbar { - align-items: center; - display: inline-flex; - flex: 1 1 180px; - flex-wrap: wrap; - gap: 6px; - justify-content: flex-end; - min-width: 0; -} - -.toolbar-button { - color: var(--muted); - font-size: 11px; - min-height: 30px; - padding: 5px 8px; -} - .toolbar-status-sr { border: 0; clip-path: inset(50%); @@ -73,16 +47,10 @@ summary:focus-visible { width: 1px; } -.toolbar-divider { - background: var(--line-strong); - height: 18px; - width: 1px; -} - .app-error { background: var(--blocked-soft); border: 1px solid var(--blocked); - border-radius: 8px; + border-radius: 6px; color: var(--blocked); font-size: 12px; font-weight: 600; @@ -93,118 +61,13 @@ summary:focus-visible { .view-panel { flex: 1 1 auto; margin-bottom: 12px; - padding-bottom: 52px; } .activity-view { background: var(--surface-1); - border: 1px dashed var(--line-strong); - border-radius: var(--radius-row); - display: grid; - gap: 6px; - padding: 18px 12px; - place-items: center; - text-align: center; -} - -.activity-view h2 { - color: var(--text); - font-size: 13px; - font-weight: 700; -} - -.activity-view p { - color: var(--faint); - font-size: 11px; -} - -.toolbar-icon, -.view-nav-icon { - fill: none; - stroke: currentcolor; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 1.8; -} - -.toolbar-icon { - height: 16px; - width: 16px; -} - -.refresh-button, -.quit-button { - align-items: center; - display: inline-flex; - justify-content: center; - min-width: 32px; - padding: 5px; -} - -.quit-button:hover:not(:disabled) { - border-color: var(--blocked); - color: var(--blocked); -} - -.view-nav { - align-items: center; - background: var(--surface-1); - border: 1px solid var(--line-strong); - border-radius: 16px; - bottom: 10px; - box-shadow: var(--shadow-pop); - display: grid; - gap: 4px; - grid-template-columns: repeat(3, minmax(0, 1fr)); - margin-top: 8px; - padding: 5px; - position: sticky; - width: 100%; - z-index: 3; -} - -@supports (background: color-mix(in srgb, black, white)) { - .view-nav { - background: color-mix(in srgb, var(--surface-1) 94%, transparent); - } -} - -.view-nav-button { - align-items: center; - background: transparent; - border-color: transparent; - border-radius: 12px; - color: var(--faint); - display: inline-flex; - font-size: 10px; - gap: 7px; - justify-content: center; - justify-self: stretch; - line-height: 1; - min-height: 40px; - min-width: 0; - padding: 6px 8px; -} - -.view-nav-button:hover:not(:disabled) { - background: var(--button-bg-hover); - border-color: var(--line); -} - -.view-nav-button[data-active="true"] { - background: var(--accent-soft); - border-color: var(--accent); - color: var(--accent); -} - -.view-nav-icon { - flex: 0 0 auto; - height: 18px; - width: 18px; -} - -.view-nav-button span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + border: 1px solid var(--line); + border-radius: 6px; + display: block; + padding: 10px; + text-align: left; } diff --git a/apps/companion/src/styles/settings.css b/apps/companion/src/styles/settings.css index bdaafa5..58693b3 100644 --- a/apps/companion/src/styles/settings.css +++ b/apps/companion/src/styles/settings.css @@ -1,19 +1,14 @@ .settings-panel { - background: var(--surface-1); - border: 1px solid var(--line); - border-radius: var(--radius-row); - box-shadow: var(--shadow-card); display: grid; - gap: 10px; + gap: 9px; margin-bottom: 12px; - padding: 10px; } .settings-panel-header { align-items: start; display: flex; - justify-content: space-between; gap: 10px; + justify-content: space-between; } .settings-panel h2 { @@ -24,24 +19,8 @@ .settings-panel p, .settings-hint { - color: var(--faint); - font-size: 11px; -} - -.settings-section { - border: 1px solid var(--line); - border-radius: 8px; - margin: 0; - padding: 9px; -} - -.settings-section legend { - color: var(--accent); + color: var(--muted); font-size: 10px; - font-weight: 700; - letter-spacing: 0.08em; - padding: 0 4px; - text-transform: uppercase; } .settings-row { @@ -53,11 +32,11 @@ .settings-row label { color: var(--text); - font-size: 12px; + font-size: 11px; } .settings-row input[type="checkbox"] { - accent-color: var(--accent); + accent-color: var(--emphasis); height: 18px; width: 18px; } @@ -89,14 +68,14 @@ .font-preview { background: var(--surface-2); border: 1px solid var(--line); - border-radius: 8px; + border-radius: 4px; color: var(--text); display: grid; - font-size: 12px; - gap: 6px; + font-size: 11px; + gap: 5px; line-height: 1.45; margin-top: 8px; - padding: 9px; + padding: 8px; } .font-preview code { @@ -105,39 +84,37 @@ } .font-preview-label { - color: var(--faint); - font-size: 10px; + color: var(--muted); + font-size: 9px; } -.theme-mode-toggle { - background: var(--surface-2); +.agent-theme-picker { border: 1px solid var(--line); - border-radius: 9px; + border-radius: 4px; display: grid; - gap: 4px; - grid-template-columns: repeat(3, minmax(0, 1fr)); - margin: 4px 0 9px; - padding: 4px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-bottom: 8px; } -.theme-mode-button { +.agent-theme-button { background: transparent; - border: 1px solid transparent; - border-radius: 7px; + border: 0; + border-radius: 0; color: var(--muted); - font-size: 11px; + font-size: 10px; font-weight: 700; - min-height: 30px; - padding: 6px; } -.theme-mode-button:hover:not(:disabled) { +.agent-theme-button + .agent-theme-button { + border-left: 1px solid var(--line); +} + +.agent-theme-button:hover:not(:disabled) { background: var(--button-bg-hover); color: var(--text); } -.theme-mode-button[data-active="true"] { - background: var(--accent-soft); - border-color: var(--accent); - color: var(--text); +.agent-theme-button[data-active="true"] { + background: var(--emphasis-soft); + color: var(--emphasis); } diff --git a/apps/companion/src/styles/status-groups.css b/apps/companion/src/styles/status-groups.css index 3673c97..057ac97 100644 --- a/apps/companion/src/styles/status-groups.css +++ b/apps/companion/src/styles/status-groups.css @@ -1,79 +1,7 @@ -.app-summary { - align-items: baseline; - display: flex; - flex-wrap: wrap; - gap: 8px; - min-width: 0; -} - -.app-inventory { - color: var(--muted); - font-family: var(--app-font-family); - font-size: 12px; - font-weight: 600; -} - -.app-badges { - display: inline-flex; - gap: 6px; -} - -.badge { - border: 1px solid var(--line); - border-radius: 999px; - font-family: var(--app-font-family); - font-size: 11px; - font-weight: 600; - line-height: 1; - padding: 3px 7px; -} - -.badge-active { - background: var(--accent-soft); - border-color: var(--accent); - color: var(--accent); -} - -.badge-blocked { - background: var(--blocked-soft); - border-color: var(--blocked); - color: var(--blocked); -} - -.badge-noti { - background: var(--notify-soft); - border-color: var(--notify); - color: var(--notify); -} - .project-group { - margin-bottom: 14px; -} - -.project-group-header { - align-items: baseline; - border-left: 1px solid var(--accent); - display: flex; - gap: 8px; - margin-bottom: 8px; - padding-left: 8px; -} - -.project-group-name { - color: var(--text); - font-size: 13px; - font-weight: 700; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.project-group-count { - color: var(--faint); - font-size: 11px; - white-space: nowrap; + margin-bottom: 10px; } -.project-group .task { - margin-left: 8px; +.project-group:last-child { + margin-bottom: 0; } diff --git a/apps/companion/src/styles/task-actions.css b/apps/companion/src/styles/task-actions.css index aebbaeb..44c191e 100644 --- a/apps/companion/src/styles/task-actions.css +++ b/apps/companion/src/styles/task-actions.css @@ -1,44 +1,45 @@ .task-actions { - align-items: center; - background: var(--surface-1); + align-items: stretch; border: 1px solid var(--line); - border-radius: 8px; + border-radius: 4px; display: flex; flex: 1 1 100%; - gap: 2px; - margin-top: 0; min-height: 32px; - width: 100%; overflow: visible; - padding: 3px; + width: 100%; } .task-action { align-items: center; background: transparent; - border-color: transparent; - border-radius: 6px; - color: var(--muted); + border: 0; + border-radius: 0; + color: var(--accent); display: inline-flex; flex: 1 1 0; - font-size: 11px; - gap: 5px; + font-size: 10px; justify-content: center; - min-height: 24px; + min-height: 30px; min-width: 0; - padding: 4px 7px; + padding: 5px 7px; touch-action: manipulation; - transition-duration: 120ms; - transition-property: background-color, border-color, color, transform; - transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + transition: + background-color 120ms ease, + color 120ms ease, + transform 120ms ease; +} + +.task-action + .task-action { + border-left: 1px solid var(--line); } .task-action:hover:not(:disabled) { - color: var(--text); + background: var(--button-bg-hover); + color: var(--accent-strong); } .task-action:active:not(:disabled) { - transform: scale(0.96); + transform: scale(0.98); } .task-action:disabled { @@ -46,17 +47,3 @@ cursor: not-allowed; opacity: 0.55; } - -.task-action-icon { - fill: none; - height: 13px; - stroke: currentcolor; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 1.9; - width: 13px; -} - -.task-action-separator { - display: none; -} diff --git a/apps/companion/src/styles/task-details.css b/apps/companion/src/styles/task-details.css index 00dc2ba..4f68867 100644 --- a/apps/companion/src/styles/task-details.css +++ b/apps/companion/src/styles/task-details.css @@ -1,145 +1,107 @@ .task { - background: var(--task-bg); + background: var(--surface-1); border: 1px solid var(--line); - border-radius: var(--radius-row); - box-shadow: var(--shadow-card); - margin-bottom: 8px; + border-radius: 4px; + margin-bottom: 6px; overflow: hidden; - transition-duration: 140ms; - transition-property: box-shadow, border-color, transform; - transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); +} + +.task:last-child { + margin-bottom: 0; } .task:hover { + background: var(--surface-2); border-color: var(--line-strong); - box-shadow: var(--shadow-card-hover); - transform: translateY(-1px); } -.task-in-progress { - border-color: color-mix(in srgb, var(--accent) 38%, var(--line-strong)); +.task[open] { + background: var(--surface-2); + border-color: var(--line-strong); } summary { cursor: default; list-style: none; - padding: 9px 10px; + padding: 7px 8px; } summary::-webkit-details-marker { display: none; } -.task-summary-line { - align-items: center; - display: grid; - gap: 8px; - grid-template-columns: 16px minmax(0, 1fr) auto auto; -} - -.task-status-rail { - align-items: center; - background: var(--surface-2); - border-radius: 999px; - display: inline-flex; - height: 16px; - justify-content: center; - width: 16px; -} - -.task-status-dot { - background: var(--faint); - border-radius: 999px; - box-shadow: 0 0 0 2px var(--surface-2); - display: block; - height: 6px; - width: 6px; -} - -.task-in-progress .task-status-dot { - background: var(--accent); - box-shadow: 0 0 0 4px var(--active-rail-bg); -} - -.task-blocked .task-status-dot { - background: var(--blocked); - box-shadow: 0 0 0 4px var(--blocked-soft); +.task[open] > summary { + background: var(--task-selected-summary-bg); + border-inline-start: var(--task-selected-accent-width) solid var(--emphasis); } -.task-review .task-status-dot { - background: var(--review); - box-shadow: 0 0 0 4px var(--review-soft); +.task-summary-line .prompt-line { + width: 100%; } -.task-done .task-status-dot { - background: var(--done); - box-shadow: 0 0 0 4px var(--done-soft); +.task-summary-line .prompt-content { + align-items: center; + display: grid; + gap: 8px; + grid-template-columns: minmax(0, 1fr) auto auto auto; } .task-name { color: var(--text); - font-size: 13px; + font-size: 12px; font-weight: 600; - letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.progress-pill, -.noti-pill { - border: 1px solid var(--line); - border-radius: 999px; - font-family: var(--app-font-family); - font-size: 11px; - font-weight: 600; - line-height: 1; - padding: 4px 7px; -} - -.progress-pill { +.task-progress, +.task-notification { color: var(--muted); + font-variant-numeric: tabular-nums; } -.task-done .progress-pill { - background: var(--done-soft); - border-color: var(--done); - color: var(--done); +.task-progress { + background: var(--surface-3); + border: 1px solid var(--line-strong); + border-radius: var(--progress-pill-radius); + color: var(--text); + font-size: var(--progress-pill-font-size); + line-height: 1; + padding: var(--progress-pill-padding); } -.noti-pill { - background: var(--notify-soft); - border-color: var(--notify); +.task-notification { color: var(--notify); + font-size: 10px; } .task-detail { border-top: 1px solid var(--line); - padding: 8px 10px 9px; + padding: 8px; } .task-detail-header { align-items: flex-start; display: flex; flex-wrap: wrap; - gap: 8px; + gap: 7px; justify-content: space-between; } .current-step-strip { - background: var(--surface-2); - border: 1px solid var(--line); - border-radius: 8px; - box-shadow: inset 1px 0 0 var(--accent); + border-block: 1px solid var(--line); + margin-top: 8px; + padding-block: 5px; +} + +.current-step-strip .prompt-content { display: grid; gap: 2px; - margin-top: 8px; - padding: 6px 8px 6px 10px; } .plan-title { - color: var(--accent); - font-family: var(--app-font-family); + color: var(--emphasis); font-size: 9px; font-weight: 600; letter-spacing: 0.06em; @@ -148,15 +110,14 @@ summary::-webkit-details-marker { .current-step { color: var(--text); - font-size: 12px; + font-size: 11px; overflow-wrap: anywhere; - white-space: normal; } .repo-chips { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 5px; list-style: none; margin: 0; padding: 0; @@ -169,16 +130,14 @@ summary::-webkit-details-marker { .repo-chip { align-items: center; - background: var(--surface-2); border: 1px solid var(--line); - border-radius: 7px; + border-radius: 4px; color: var(--muted); display: inline-flex; - font-family: var(--app-font-family); - font-size: 11px; + font-size: 10px; gap: 5px; max-width: 100%; - padding: 4px 6px; + padding: 3px 5px; } .repo-dirty { @@ -194,7 +153,7 @@ summary::-webkit-details-marker { } .repo-branch { - color: var(--faint); + color: var(--muted); } .repo-dot { @@ -205,10 +164,10 @@ summary::-webkit-details-marker { .steps { border-left: 1px solid var(--line); color: var(--muted); - font-size: 12px; + font-size: 11px; line-height: 1.45; list-style: none; - margin: 9px 0 0 7px; + margin: 8px 0 0 6px; padding: 0 0 0 8px; } @@ -221,6 +180,7 @@ summary::-webkit-details-marker { } .step-marker { + border: 1px solid var(--line); border-radius: 999px; box-sizing: border-box; display: inline-block; @@ -230,12 +190,10 @@ summary::-webkit-details-marker { .step-depth-0 .step-marker { border-radius: 2px; height: 8px; - margin-top: 5px; width: 8px; } .step-depth-1 .step-marker { - border-radius: 999px; height: 7px; width: 7px; } @@ -247,36 +205,20 @@ summary::-webkit-details-marker { width: 5px; } -.step-marker-done { - background: var(--faint); - box-shadow: 0 0 0 2px var(--line); -} - +.step-marker-done, .step-depth-0.step-item-done .step-marker { background: var(--faint); - box-shadow: 0 0 0 2px var(--line); -} - -.step-depth-1.step-item-done .step-marker { - box-shadow: 0 0 0 2px var(--line); -} - -.step-depth-2.step-item-done .step-marker { - box-shadow: 0 0 0 1px var(--line); } .step-marker-todo { background: var(--surface-2); - border: 1px solid var(--line-strong); -} - -.step-depth-0.step-item-todo .step-marker { border-color: var(--line-strong); - box-shadow: 0 0 0 2px var(--line); } .step-text { - overflow-wrap: anywhere; + overflow-wrap: break-word; + text-wrap: balance; + word-break: keep-all; } .step-item-done .step-text { @@ -287,20 +229,21 @@ summary::-webkit-details-marker { color: var(--text); } +.error, +.empty { + border-radius: 4px; + font-size: 12px; + padding: 8px; +} + .error { background: var(--blocked-soft); border: 1px solid var(--blocked); - border-radius: 8px; color: var(--blocked); - font-size: 12px; - padding: 8px; } .empty { border: 1px dashed var(--line-strong); - border-radius: var(--radius-row); color: var(--muted); - font-size: 12px; - padding: 14px; text-align: center; } diff --git a/apps/companion/src/styles/themes.css b/apps/companion/src/styles/themes.css index 4884ed1..bc4cfa2 100644 --- a/apps/companion/src/styles/themes.css +++ b/apps/companion/src/styles/themes.css @@ -1,99 +1,83 @@ -main[data-theme="catppuccin-dark"] { - --surface-0: #1e1e2e; - --surface-1: #181825; - --surface-2: #313244; - --surface-3: #45475a; - --line: rgba(205, 214, 244, 0.12); - --line-strong: rgba(205, 214, 244, 0.24); - --text: #cdd6f4; - --muted: #a6adc8; - --faint: #7f849c; - --accent: #cba6f7; - --accent-strong: #ddb6f9; - --accent-soft: rgba(203, 166, 247, 0.18); - --blocked: #f38ba8; - --blocked-soft: rgba(243, 139, 168, 0.18); - --review: #f5c2e7; - --review-soft: rgba(245, 194, 231, 0.16); - --done: #a6e3a1; - --done-soft: rgba(166, 227, 161, 0.16); - --notify: #f9e2af; - --notify-soft: rgba(249, 226, 175, 0.16); - --button-bg: rgba(205, 214, 244, 0.07); - --button-bg-hover: rgba(205, 214, 244, 0.13); - --shell-bg: - radial-gradient(120% 80px at 50% 0, rgba(203, 166, 247, 0.1), transparent), - var(--surface-0); - --shell-topline: linear-gradient( - 90deg, - transparent, - #cba6f7, - #f5c2e7, - transparent - ); - --current-step-bg: - linear-gradient(135deg, var(--accent-soft), transparent 70%), - var(--surface-3); - --cal-1: #cba6f7; - --cal-2: #89b4fa; - --cal-3: #a6e3a1; - --cal-4: #f9e2af; - --cal-5: #f5c2e7; - --cal-6: #94e2d5; +main[data-theme="claude"] { + --surface-0: #0e0e0e; + --surface-1: #131313; + --surface-2: #1b1b1b; + --surface-3: #252525; + --line: #3a3a3a; + --line-strong: #565656; + --text: #c0caf5; + --muted: #949494; + --faint: #626262; + --accent: #cd694a; + --accent-strong: #e08567; + --accent-soft: rgba(205, 105, 74, 0.14); + --emphasis: #cd694a; + --emphasis-soft: rgba(192, 202, 245, 0.08); + --task-selected-summary-bg: rgba(192, 202, 245, 0.06); + --blocked: #f7768e; + --blocked-soft: rgba(247, 118, 142, 0.14); + --review: #bb9af7; + --review-soft: rgba(187, 154, 247, 0.14); + --done: #87d787; + --done-soft: rgba(135, 215, 135, 0.14); + --notify: #e0af68; + --notify-soft: rgba(224, 175, 104, 0.14); + --button-bg: rgba(192, 202, 245, 0.06); + --button-bg-hover: rgba(192, 202, 245, 0.12); + --current-step-bg: rgba(192, 202, 245, 0.08); + --cal-1: #cd694a; + --cal-2: #7dcfff; + --cal-3: #87d787; + --cal-4: #e0af68; + --cal-5: #bb9af7; + --cal-6: #f7768e; + --cal-bg-1: rgba(205, 105, 74, 0.18); + --cal-bg-2: rgba(125, 207, 255, 0.18); + --cal-bg-3: rgba(135, 215, 135, 0.18); + --cal-bg-4: rgba(224, 175, 104, 0.18); + --cal-bg-5: rgba(187, 154, 247, 0.18); + --cal-bg-6: rgba(247, 118, 142, 0.18); } -main[data-theme="breakfast-light"] { - --surface-0: #ffffff; - --surface-1: #fbfcff; - --surface-2: #f4f6fb; - --surface-3: #e9edf5; - --line: rgba(36, 38, 51, 0.1); - --line-strong: rgba(36, 38, 51, 0.2); - --text: #242633; - --muted: #555b6f; - --faint: #81879a; - --accent: #7c3aed; - --accent-strong: #5b21b6; - --accent-soft: rgba(124, 58, 237, 0.12); - --blocked: #d20f39; - --blocked-soft: rgba(210, 15, 57, 0.12); - --review: #8839ef; - --review-soft: rgba(136, 57, 239, 0.12); - --done: #2f9e44; - --done-soft: rgba(47, 158, 68, 0.12); - --notify: #d97706; - --notify-soft: rgba(217, 119, 6, 0.1); - --button-bg: rgba(36, 38, 51, 0.04); - --button-bg-hover: rgba(36, 38, 51, 0.08); - --shell-bg: - radial-gradient(120% 120px at 50% 0, rgba(124, 58, 237, 0.08), transparent), - radial-gradient( - 90% 160px at 100% 10%, - rgba(203, 166, 247, 0.08), - transparent - ), - var(--surface-0); - --shell-topline: linear-gradient( - 90deg, - transparent, - #c4b5fd, - #7c3aed, - transparent - ); - --current-step-bg: - linear-gradient(135deg, var(--accent-soft), transparent 72%), - var(--surface-3); - --cal-1: #7c3aed; - --cal-2: #1d4ed8; - --cal-3: #2f9e44; - --cal-4: #d97706; - --cal-5: #db2777; - --cal-6: #0d9488; -} - -main[data-theme$="-light"] { - --shadow-soft: rgba(30, 35, 50, 0.1); - --shadow-strong: rgba(30, 35, 50, 0.16); +main[data-theme="codex"] { + --surface-0: #0f0f0f; + --surface-1: #141414; + --surface-2: #1c1c1c; + --surface-3: #262626; + --line: #3a3a3a; + --line-strong: #565656; + --text: #ededed; + --muted: #949494; + --faint: #5d5d5d; + --accent: #5cc2e0; + --accent-strong: #8bdcf2; + --accent-soft: rgba(92, 194, 224, 0.14); + --emphasis: #ededed; + --emphasis-soft: rgba(237, 237, 237, 0.1); + --task-selected-summary-bg: rgba(237, 237, 237, 0.06); + --blocked: #f2a0a0; + --blocked-soft: rgba(242, 160, 160, 0.14); + --review: #bb9af7; + --review-soft: rgba(187, 154, 247, 0.14); + --done: #abdfa7; + --done-soft: rgba(171, 223, 167, 0.14); + --notify: #f6e2b7; + --notify-soft: rgba(246, 226, 183, 0.14); + --button-bg: rgba(237, 237, 237, 0.06); + --button-bg-hover: rgba(237, 237, 237, 0.12); + --current-step-bg: var(--emphasis-soft); + --cal-1: #5cc2e0; + --cal-2: #f6e2b7; + --cal-3: #abdfa7; + --cal-4: #a7a7a7; + --cal-5: #f2a0a0; + --cal-6: #bb9af7; + --cal-bg-1: rgba(92, 194, 224, 0.18); + --cal-bg-2: rgba(246, 226, 183, 0.18); + --cal-bg-3: rgba(171, 223, 167, 0.18); + --cal-bg-4: rgba(167, 167, 167, 0.18); + --cal-bg-5: rgba(242, 160, 160, 0.18); + --cal-bg-6: rgba(187, 154, 247, 0.18); } main[data-font="sf-mono"] { @@ -114,14 +98,3 @@ main[data-font="jetbrains-mono"] { "JetBrains Mono", ui-monospace, "SFMono-Regular", "Menlo", "Monaco", "Consolas", monospace; } - -main::before { - background: var(--shell-topline); - content: ""; - display: block; - height: 1px; - left: 12px; - position: absolute; - right: 12px; - top: 0; -} diff --git a/apps/companion/src/ui/AgentHeader.tsx b/apps/companion/src/ui/AgentHeader.tsx new file mode 100644 index 0000000..645d2e7 --- /dev/null +++ b/apps/companion/src/ui/AgentHeader.tsx @@ -0,0 +1,76 @@ +import type { CompanionTheme } from "../application/preferences"; +import type { MenuSummary } from "../application/state"; + +export type AgentHeaderProps = { + readonly theme: CompanionTheme; + readonly summary: MenuSummary; + readonly status: string; + readonly onRefresh: () => void; + readonly onQuit: () => void; +}; + +function AgentInventory({ summary }: { readonly summary: MenuSummary }) { + return ( + + {summary.projectCount}{" "} + {summary.projectCount === 1 ? "project" : "projects"} ·{" "} + {summary.taskCount} {summary.taskCount === 1 ? "task" : "tasks"} + + ); +} + +function AgentControls({ + status, + onRefresh, + onQuit, +}: Pick) { + return ( +
+ + {status} + + + +
+ ); +} + +export function AgentHeader({ + theme, + summary, + status, + onRefresh, + onQuit, +}: AgentHeaderProps) { + const controls = { status, onRefresh, onQuit }; + + return ( +
+
+
+

Workbranch Companion

+ +
+ +
+
+ ); +} diff --git a/apps/companion/src/ui/AgentTabs.tsx b/apps/companion/src/ui/AgentTabs.tsx new file mode 100644 index 0000000..ea159cd --- /dev/null +++ b/apps/companion/src/ui/AgentTabs.tsx @@ -0,0 +1,36 @@ +export type CompanionView = "main" | "activity" | "settings"; + +export type AgentTabsProps = { + readonly currentView: CompanionView; + readonly onViewChange: (view: CompanionView) => void; +}; + +type AgentTab = { + readonly view: CompanionView; + readonly label: string; +}; + +const AGENT_TABS: readonly AgentTab[] = [ + { view: "main", label: "Main" }, + { view: "activity", label: "Activity" }, + { view: "settings", label: "Settings" }, +]; + +export function AgentTabs({ currentView, onViewChange }: AgentTabsProps) { + return ( + + ); +} diff --git a/apps/companion/src/ui/AgentThemePicker.tsx b/apps/companion/src/ui/AgentThemePicker.tsx new file mode 100644 index 0000000..4eb7c1d --- /dev/null +++ b/apps/companion/src/ui/AgentThemePicker.tsx @@ -0,0 +1,32 @@ +import { + COMPANION_THEME_OPTIONS, + type CompanionTheme, +} from "../application/preferences"; + +type AgentThemePickerProps = { + readonly value: CompanionTheme; + readonly onChange: (theme: CompanionTheme) => void; +}; + +export function AgentThemePicker({ value, onChange }: AgentThemePickerProps) { + return ( +
+ {COMPANION_THEME_OPTIONS.map((option) => { + const selected = option.value === value; + return ( + + ); + })} +
+ ); +} diff --git a/apps/companion/src/ui/AppSummary.tsx b/apps/companion/src/ui/AppSummary.tsx deleted file mode 100644 index 99a1124..0000000 --- a/apps/companion/src/ui/AppSummary.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { MenuSummary } from "../application/state"; - -function plural(count: number, word: string): string { - return count === 1 ? word : `${word}s`; -} - -type Props = { - readonly summary: MenuSummary; -}; - -export function AppSummary({ summary }: Props) { - const { projectCount, taskCount, active, blocked, notifications } = summary; - return ( -
- - {taskCount === 0 - ? "No tasks" - : `${projectCount} ${plural(projectCount, "project")} · ${taskCount} ${plural(taskCount, "task")}`} - - - {active > 0 ? ( - - RUN {active} - - ) : null} - {blocked > 0 ? ( - - BLK {blocked} - - ) : null} - {notifications > 0 ? ( - - NOTI {notifications} - - ) : null} - -
- ); -} diff --git a/apps/companion/src/ui/AppToolbar.tsx b/apps/companion/src/ui/AppToolbar.tsx deleted file mode 100644 index 2ec585a..0000000 --- a/apps/companion/src/ui/AppToolbar.tsx +++ /dev/null @@ -1,44 +0,0 @@ -type Props = { - readonly onRefresh: () => void; - readonly onQuit: () => void; - readonly status: string; -}; - -export function AppToolbar({ onRefresh, onQuit, status }: Props) { - return ( -
- - {status} - - - -
- ); -} diff --git a/apps/companion/src/ui/ProjectGroup.tsx b/apps/companion/src/ui/ProjectGroup.tsx index 19a43ce..2e81463 100644 --- a/apps/companion/src/ui/ProjectGroup.tsx +++ b/apps/companion/src/ui/ProjectGroup.tsx @@ -1,33 +1,32 @@ +import type { CompanionTheme } from "../application/preferences"; import type { ProjectGroup as ProjectGroupModel } from "../application/state"; -import type { Task } from "../domain/model"; -import { type TaskActionKind, TaskRow } from "./TaskRow"; +import { type TaskActionHandler, TaskRow } from "./TaskRow"; +import { TerminalPanel } from "./TerminalPanel"; -type Props = { +type ProjectGroupProps = { readonly group: ProjectGroupModel; - readonly onAction: (root: string, task: Task, kind: TaskActionKind) => void; + readonly theme: CompanionTheme; + readonly onAction: TaskActionHandler; }; -export function ProjectGroup({ group, onAction }: Props) { +export function ProjectGroup({ group, theme, onAction }: ProjectGroupProps) { const count = group.rows.length; return ( -
-
- - {group.project} - - - {count} {count === 1 ? "task" : "tasks"} - -
+ {group.rows.map((row) => ( ))} -
+ ); } diff --git a/apps/companion/src/ui/PromptLine.tsx b/apps/companion/src/ui/PromptLine.tsx new file mode 100644 index 0000000..f2d7990 --- /dev/null +++ b/apps/companion/src/ui/PromptLine.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from "react"; +import type { CompanionTheme } from "../application/preferences"; + +export type PromptLineProps = { + readonly theme: CompanionTheme; + readonly current?: boolean; + readonly children: ReactNode; +}; + +export function PromptLine({ + theme, + current = false, + children, +}: PromptLineProps) { + return ( +
+ + {children} +
+ ); +} diff --git a/apps/companion/src/ui/SettingsPanel.tsx b/apps/companion/src/ui/SettingsPanel.tsx index 6c4b4ff..ff4295d 100644 --- a/apps/companion/src/ui/SettingsPanel.tsx +++ b/apps/companion/src/ui/SettingsPanel.tsx @@ -1,16 +1,13 @@ -import type { - CompanionPreferences, - CompanionResolvedThemeMode, -} from "../application/preferences"; +import type { CompanionPreferences } from "../application/preferences"; import { COMPANION_FONT_OPTIONS, - COMPANION_THEME_MODE_OPTIONS, isCompanionFont, } from "../application/preferences"; +import { AgentThemePicker } from "./AgentThemePicker"; +import { TerminalPanel } from "./TerminalPanel"; type Props = { readonly preferences: CompanionPreferences; - readonly systemThemeMode: CompanionResolvedThemeMode; readonly launchAtLogin: boolean; readonly launchAtLoginLoading: boolean; readonly onLaunchAtLoginChange: (enabled: boolean) => void; @@ -19,7 +16,6 @@ type Props = { export function SettingsPanel({ preferences, - systemThemeMode, launchAtLogin, launchAtLoginLoading, onLaunchAtLoginChange, @@ -30,10 +26,6 @@ export function SettingsPanel({ ); const fontFamily = fontOption?.cssFamily ?? preferences.font; const fontName = fontOption?.label ?? preferences.font; - const modeHint = - preferences.themeMode === "system" - ? `System (${systemThemeMode === "dark" ? "Dark" : "Light"} now)` - : `${preferences.themeMode === "dark" ? "Dark" : "Light"} mode`; return (
@@ -43,8 +35,7 @@ export function SettingsPanel({

Companion preferences

-
- Startup +
-
-
- Font + +