Files
AgentHub/docs/plans/workspace-redesign/03-appearance-and-sounds.md
T
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

132 lines
5.4 KiB
Markdown

# 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:
```ts
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`:
```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.