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).
5.4 KiB
Phase 3 — Two Orca-derived named themes and notification sounds
One PR, after phase 2. Small.
Goal
- 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.
- 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 withoutsoundloads as"none",soundByTyperound-trip.useNotificationstest:playSoundcalled with the per-type value, not called for"none"; stubHTMLMediaElement.prototype.play.- Theme: picker lists the two new names;
data-themeattribute 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.