a6bd33bcff
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).
132 lines
5.4 KiB
Markdown
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.
|