Files
nntrivi2001 a6bd33bcff feat: workspaces own their sessions; desktop parity; sidebar and settings cleanup
Workspace sessions (docs/plans/desktop-parity, phases 1-5 and 7):
- sessions.lane_id: a workspace lists only the sessions its panes held,
  stamped by bindRunSession (first writer wins, nested `claude -p`
  excluded); GET /api/sessions?lane=&archived=
- session row state dot, double-click rename, archive (sessions.archived_at,
  hides only); the activity bar's duplicate Sessions tab removed
- Fork beside Resume (--fork-session, Claude only), Ctrl/Cmd+click a live
  session opens it in a second pane, a diff line's comment is pasted into
  the workspace's last-focused pane (lib/panePaste.ts)

dashboard_runs stability:
- a pane's previous run is archived as <pane>@<started_at> instead of being
  overwritten by INSERT OR REPLACE
- a run is flagged abandoned only once its tmux session is gone, at boot and
  on the run-heal beat (panes outlive a server restart)

UI:
- sidebar: no footer, language or theme block; small collapse button in the
  brand corner; version + update check in the connection details modal, a
  status-bar dot while a release is out; language in Settings -> General
- one Switch component for every switch, the themed Checkbox for every
  checkbox (no native checkboxes)

Also: AI Tools catalog trimmed to 10 built-ins (custom-file prompt modes
tested through fixtures), and the workspace plans under docs/plans/ (the
multi-user server plan lives in the AgentHub-Server repo).
2026-10-02 13:19:50 +07:00

5.4 KiB

Phase 3 — Two Orca-derived named themes and notification sounds

One PR, after phase 2. Small.

Goal

  1. Offer Orca's neutral palette as two optional named themes — "Orca Dark" and "Orca Light" — in the existing Appearance picker. Defaults do not change; nothing else about the theme system changes.
  2. Play a short sound with a browser notification, per category, user-chosen.

1. Themes

client/src/hooks/useTheme.ts — extend the registry only:

export const THEMES = {
  dark: ["default", "darcula", "apple-dark", "orca-dark"],
  light: ["default", "apple-light", "intellij-light", "canvas", "orca-light"],
} as const …

client/src/index.css — two override blocks shaped exactly like .dark[data-theme="darcula"] (every token the other named themes set, no more, no fewer; comment each with the hex). Values mapped from Orca's src/renderer/src/assets/main.css (.dark / :root) onto AgentHub's roles:

AgentHub token Orca Dark Orca Light
--surface-0 (page) #0a0a0a #ffffff
--surface-1 #111111 #fafafa
--surface-2 #171717 (card) #f5f5f5 (muted)
--surface-3 (.card) #1c1c1c #ffffff
--surface-4 #262626 (secondary/muted) #ececec
--surface-5 #404040 (accent/hover) #e5e5e5
--border #1f1f1f (≈ white 7 % on #0a0a0a) #e5e5e5
--border-light #2e2e2e (≈ white 15 %) #d4d4d4
--accent #e5e5e5 (Orca primary) — check contrast of accent-on-surface text; if the UI uses text-accent on surface-3, use #3b82f6 instead and note it #171717 (same caveat)
--accent-hover #fafafa #262626
--fg-primary #fafafa #0a0a0a
--fg-secondary #d4d4d4 #404040
--fg-muted #a1a1a1 #737373
--status-success #86efac #15803d
--status-warning #eab308 #ca8a04
--status-danger #ff6568 #e40014
--syn-* copy from apple-dark copy from apple-light

The light theme's surfaces must still rise in lightness with their index as index.css documents; the table above honours that except surface-3 = #ffffff on surface-0 = #ffffff — set --surface-0 to #f5f5f5 and --surface-1 to #fafafa so the card lifts. State the final values in the PR.

Verify with the existing contrast rule (fg-muted ≥ 4.5:1 on surface-3, status labels ≥ 4.5:1 on their densest chip) — the Appearance swatch test (Sidebar.theme.test.tsx / AppearanceSettings tests) covers the picker; add the two names to whatever list those tests assert.

terminalTheme.ts already derives the xterm palette from the tokens; confirm it reads the new blocks live when switching.

i18n: settings.json appearance.themes.orca-dark / orca-light (en: "Orca Dark"/"Orca Light", vi same).

2. Notification sounds

Prefs

client/src/lib/notifPrefs.ts:

export type NotifSound = "none" | "chime" | "pop" | "alert";
export interface NotifPrefs {
  …existing…
  sound: NotifSound;                     // global default
  soundByType?: Partial<Record<keyof Omit<NotifPrefs, "enabled" | "sound" | "soundByType">, NotifSound>>;
}
defaultNotifPrefs.sound = "none";

loadNotifPrefs already spreads defaults over stored JSON, so existing users get "none".

Assets

client/public/sounds/{chime,pop,alert}.wav, each ≤ 20 KB, generated — not downloaded — by scripts/gen-notification-sounds.js (Node, PCM 16-bit 22.05 kHz mono, two or three sine partials with a short decay; commit the WAVs and the script; the script carries the file header). No licence question, no network.

Playback

client/src/lib/notify.ts: playSound(kind) — a module-level Map<NotifSound, HTMLAudioElement> created lazily, audio.currentTime = 0; audio.play().catch(() => {}) (autoplay may be refused before a user gesture; that is fine). useNotifications.ts: in notify(), after deciding to show (native, push or local fallback), resolve soundByType[type] ?? sound and play it when not "none". Play whenever a notification is shown, visible tab or not; the per-type setting is the user's volume control. No focus heuristics.

Settings UI

Phase-2 notifications section: a SettingsRow "Sound" with a <select> (None / Chime / Pop / Alert) and a ▶ preview button; under each existing per-type switch, a small "Use default · Chime · Pop · Alert · None" segmented control (SettingsSegmented) that writes soundByType[type]. i18n: notifications.sound.*.

Tests

  • notifPrefs.test.ts: defaults, legacy JSON without sound loads as "none", soundByType round-trip.
  • useNotifications test: playSound called with the per-type value, not called for "none"; stub HTMLMediaElement.prototype.play.
  • Theme: picker lists the two new names; data-theme attribute set; snapshot of Appearance regenerated once.

Docs

client/README.md (themes list, sounds), README.md feature list, CLAUDE.md marker ("named themes gained orca-dark/orca-light; a sound per notification category, assets generated by scripts/gen-notification-sounds.js").

Acceptance

  • Switching to Orca Dark/Light re-colours every surface, the xterm palette and the diff view with no hardcoded colour leaking (walk Dashboard, Sessions, Workspace with the Changes panel open, Settings).
  • A turn-complete notification with sound "Pop" plays once; with "None" plays nothing; preview button plays immediately.