Skip to content

Commit ca0452b

Browse files
committed
Add plans for palette, branding, and themes
Updates the planning overview with a new third wave (plans 26–28) and adds three detailed roadmap docs: Command Palette v2, purple phone brand identity rollout, and Appearance v2 theming/tokens/white-label work. Each plan captures dependencies, phased delivery, design decisions, and definition-of-done criteria aligned with prior plans.
1 parent 0a9f22f commit ca0452b

4 files changed

Lines changed: 401 additions & 0 deletions

File tree

‎docs/plans/00-overview.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ in their plans — places DeviceKit would lead rather than follow.
4646
| 24 | [Fleet health & remediation](24-fleet-health-and-remediation.md) | `fleet_sweep` bounded fan-out + doctor/repair allowlist + drift + z-score anomaly + regression forecast (finishes Phase 22); reframes "scale" as discipline not multi-node | 05, 07, 08, 06 |
4747
| 25 | [Agent lifecycle & backup/DR](25-agent-lifecycle-and-backup.md) | Agent-enforced read-only primitive allowlist, capability negotiation, **OTA agent updates** (greenfield), onboarding state machine, agent-plugin contract, backup + **restore drills** | 07, 05, 08 |
4848

49+
### Third wave (26–28) — palette parity & visual identity
50+
51+
Plans 10 and 12 shipped the first-generation command palette and theming; ServerKit's have
52+
since grown past them, and DeviceKit never had a real logo. These close both gaps.
53+
54+
| # | Plan | What it borrows | Depends on |
55+
|---|------|-----------------|------------|
56+
| 26 | [Command palette v2](26-command-palette-v2.md) | `F1`/`Ctrl+Shift+P` bindings, settings-card index + deep-link flash, frecency ranking, backend `/search` omnisearch (extends 10) | 10, 12, 20 |
57+
| 27 | [Brand identity: purple phone logo](27-brand-identity-purple-phone.md) | Master-SVG pipeline (squircle + indigo gradient), accent-tinted JSX logo, favicon redraw, Android adaptive-icon alignment, purple default accent | 12, 25 (soft) |
58+
| 28 | [Appearance v2: themes & tokens](28-appearance-v2-themes-and-tokens.md) | One CSS-token sheet, dark/light/system via `data-theme` + `matchMedia`, 8-preset + custom accent picker, white-label (extends 12) | 12, 27 |
59+
4960
## Executing a plan
5061

5162
- A local **plan-executor prompt** (`docs/plans/prompt.md`, git-ignored because it
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Plan 26 — Command Palette v2: F1, Omnisearch & Settings Deep-Links
2+
3+
**Status:** 📝 planned
4+
**Inspired by:** ServerKit's current palette stack — `layouts/DashboardLayout.jsx` (three
5+
open bindings), `components/CommandPalette.jsx` (prefix modes, category weights),
6+
`data/settingsIndex.js` + `hooks/useSettingFocus.js` (settings deep-links that *flash* the
7+
card), `utils/paletteFrecency.js` (frecency ranking), and `backend/app/services/search_service.py`
8+
(a real `/search` entity omnisearch).
9+
**Depends on:** 10 (✅ shipped — this plan **extends** it, not a rewrite), 12 (settings shell),
10+
20 (RBAC — gating admin-only results), 16 (docs suite, soft — `?` docs mode)
11+
12+
## Problem
13+
14+
Plan 10 shipped a solid `Ctrl/Cmd+K` palette (cmdk 1.1.1, fuzzy scorer, pages + devices +
15+
automations + actions + extension entries + FQL `>` mode). Since then ServerKit's palette
16+
grew four things DeviceKit's lacks, and the user's number-one ask is the first:
17+
18+
1. **More open bindings** — `F1` and `Ctrl/Cmd+Shift+P` (VS Code muscle memory), not just `Ctrl+K`.
19+
2. **Settings omnisearch** — every settings *card* is indexed; selecting one deep-links to
20+
`/settings/<tab>?focus=setting:<id>` and the card scrolls into view and flashes.
21+
3. **Frecency** — DeviceKit keeps "last 6 recents"; ServerKit ranks by frequency+recency with
22+
a 14-day exponential half-life, and blends that score into live ranking.
23+
4. **Backend `/search`** — entities are fetched via one authz-scoped omnisearch endpoint
24+
instead of client-side fetch-all-on-open (which stops scaling past a few dozen devices).
25+
26+
## How ServerKit does it (the parts worth porting)
27+
28+
- **Bindings** live in one `document` keydown in the layout: `cmdK || cmdShiftP || f1` toggles;
29+
`e.preventDefault()` on F1 is required (stops browser help).
30+
- **Prefix modes:** bare = everything, `>` = actions only, `?` = docs only; footer advertises
31+
`↵ open · > actions · ? docs · esc close`. *(DeviceKit already uses `>` for FQL — keep that;
32+
see Decisions.)*
33+
- **Settings index:** a flat registry `{ id, label, description, keywords, tab, adminOnly }` →
34+
path `/settings/<tab>?focus=setting:<id>`; a `useSettingFocus()` hook on the settings page
35+
registers cards by id, scrolls the target into view, and applies a 2 s ring/wash keyframe.
36+
A lint script (`scripts/check-settings-index.mjs`) fails CI if a settings tab has zero entries.
37+
- **Ranking:** cmdk's filter disabled (`shouldFilter={false}`); hand scorer (substring
38+
fast-path + prefix/word-boundary bonuses, else ordered subsequence) — DeviceKit already
39+
ported this in plan 10. New: final score = `fuzzy + CATEGORY_WEIGHT + min(frecency, 10)`,
40+
fixed `GROUP_ORDER`, `PER_GROUP_CAP = 6`, `OVERALL_CAP = 30`.
41+
- **Backend omnisearch:** `SearchService.search(user, term, workspace)` returns rows
42+
`{type, label, sublabel, path}` across all entity tables, authz-scoped; frontend debounces
43+
200 ms, min 2 chars, maps `type → category`.
44+
45+
## Design for DeviceKit
46+
47+
### Part 1 — Bindings + frecency (client-only)
48+
49+
- Add `F1` and `Ctrl/Cmd+Shift+P` alongside `Ctrl/Cmd+K` in `CommandPalette.jsx:141-152`
50+
(the listener already exists there; keep it there rather than a new hook).
51+
- Replace the `devicekit_palette_recents` last-6 list with `utils/paletteFrecency.js`
52+
(copy ServerKit's ~40-line util: exponential decay, 14-day half-life, `recordUse` /
53+
`recentIds(8)` / `frecencyScore`). Key: `devicekit:palette:frecency`.
54+
- Footer hint row: `↵ open · > query fleet · ? docs · esc`.
55+
56+
### Part 2 — Settings index + deep-link flash + authz
57+
58+
- `data/settingsIndex.js`: one entry per settings *card* across the ~14 panes in
59+
`components/settings/` (General, ApiAccess, Security, Users, ApiKeysPane, Workspaces,
60+
Vault, AuditLog, AiSettings, Streaming, Bundles, Notifications, Appearance, About).
61+
- `hooks/useSettingFocus.js` + an `.is-setting-focused` keyframe in `index.css`; Settings
62+
panes spread `register(id)` onto each card container.
63+
- `usePaletteAuthz()`: drop `adminOnly` entries for non-admins (`useAuth().isAdmin`) and
64+
respect workspace nav visibility — the palette must never surface a page the sidebar hides.
65+
- Optional but cheap: a `scripts/check-settings-index.mjs` lint mirroring ServerKit's.
66+
67+
### Part 3 — Backend `/search` omnisearch
68+
69+
- `SearchMixin` (`backend/devicekit/mixins/search.py`, composed before `ApiAppMixin` per the
70+
house pattern) + `GET /search?q=` blueprint. Entities: **devices** (name/serial/model),
71+
**automations**, **profiles**, **fleet groups**, **extensions**, **jobs** (recent),
72+
**users/workspaces** (admin only). Each row `{type, label, sublabel, path}`; scope by the
73+
caller's workspace + role (plan 20's `scope_query`).
74+
- Frontend: in `all` mode, ≥2 chars, debounce 200 ms; replaces the fetch-everything-on-open
75+
device/automation providers. Map `type → category` (Devices, Automations, Profiles, Groups,
76+
Extensions, Jobs) with lucide icons and category weights (Settings=6, Pages/Actions=4,
77+
entities=1); entity hits floor at score 0 so a serial fragment always shows.
78+
79+
## Phases
80+
81+
| Phase | Delivers | Proves |
82+
|---|---|---|
83+
| 1 | F1 + Ctrl/Cmd+Shift+P bindings, frecency ranking replacing recents, footer hints | `F1` from any view opens the palette; most-used items float to the top of the empty screen |
84+
| 2 | Settings card index + `?focus=setting:` deep-link flash + authz gating | typing "retention" → enter lands on `/settings/bundles` with the retention card flashing; non-admins never see Vault/Users entries |
85+
| 3 | `SearchMixin` + `GET /search`, async entity provider with weights/caps | with 200 devices, typing 3 chars of a serial returns in one debounced request instead of pre-fetching every table |
86+
87+
Phases are independent; 1 is a one-sitting win and should land first since it's the direct ask.
88+
89+
## Decisions to make while executing (log, don't stop)
90+
91+
- **`>` prefix collision:** DeviceKit's `>` is FQL (plan 10's differentiator); ServerKit's is
92+
actions-only. **Keep `>` = FQL**, leave actions in the bare mode, add `?` = docs. Log it.
93+
- **Docs mode targets:** plan 16's docs suite if routable in-app, else external repo links.
94+
Hide the category if neither exists yet.
95+
- **`/search` vs `/api/v1`:** ship as an internal panel route first; mirroring into the public
96+
API (plan 21) is follow-up.
97+
98+
## Definition of done
99+
100+
From any view, **F1** (or `Ctrl+K`, or `Ctrl+Shift+P`) opens the palette; typing part of a
101+
device serial hits `/search` and enter lands on `/node/<id>`; typing part of a settings card
102+
name lands on the right tab with the card flashing; items you use daily rank above things you
103+
touched once; `> battery < 20` still runs FQL exactly as plan 10 shipped it.
104+
105+
## Out of scope
106+
107+
- **Cross-device frecency sync.** localStorage-only, like ServerKit.
108+
- **Palette theming.** Visual refresh rides plans 27/28.
109+
110+
## ServerKit source map (for implementers)
111+
112+
- Bindings: `frontend/src/layouts/DashboardLayout.jsx`
113+
- Orchestrator + weights/caps/modes: `frontend/src/components/CommandPalette.jsx`
114+
- Registries: `frontend/src/data/palettePages.js`, `data/commandActions.js`, `data/settingsIndex.js`
115+
- Utils/hooks: `utils/paletteScore.js`, `utils/paletteFrecency.js`, `hooks/usePaletteAuthz.js`, `hooks/useSettingFocus.js`
116+
- Backend: `backend/app/api/search.py`, `backend/app/services/search_service.py`
117+
- Styling: `styles/components/_command-palette.scss`, `styles/components/_ui.scss` (~728–856)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Plan 27 — Brand Identity: the Purple Phone Logo, Everywhere
2+
3+
**Status:** 📝 planned
4+
**Inspired by:** ServerKit's branding pipeline — one master SVG
5+
(`frontend/src/assets/ServerKitLogo.svg`: 2048 viewBox, white squircle tile, diagonal
6+
indigo gradient `#6366f1 → #4f46e5`), a JSX twin (`components/ServerKitLogo.jsx`) whose
7+
gradient reads **CSS accent variables** so the mark re-tints with the user's accent (unique
8+
gradient id per instance via `useId()` — shared ids collide and paint nothing), a
9+
hand-simplified 32×32 stroke favicon, and `serverkit-agent/packaging/icons/generate_icons.py`
10+
rasterizing the master into `.ico`/tray icons.
11+
**Depends on:** 12 (accent CSS vars — the JSX logo tints from them), 25 (soft — OTA can
12+
ship the re-branded APK fleet-wide once phase 3 rebuilds it)
13+
14+
## Problem
15+
16+
DeviceKit has **three different marks and two different brand colors**:
17+
18+
- Web sidebar + login: the lucide `Layers` glyph in a white square (`App.jsx:145-149`,
19+
`auth/Login.jsx:16-21`) — no `Logo` component exists.
20+
- Web favicon: a *different* custom phone-outline SVG, emerald `#10b981` on `#050505`.
21+
- **Android agent: already the target identity** — adaptive icon with solid indigo
22+
`#6366F1` background + white phone glyph (`ic_launcher_background.xml` /
23+
`ic_launcher_foreground.xml`), app theme `colorPrimary #6366F1`, dark purple surfaces
24+
(`#0F0D1A` / `#1E1B2E`).
25+
26+
The "og logo" ask = make the agent's purple-tile-with-phone the canonical mark, draw it
27+
properly as one master SVG in ServerKit's style, and roll it out to every surface.
28+
29+
## Design for DeviceKit
30+
31+
### Part 1 — The master asset + Logo component
32+
33+
- `frontend/src/assets/DeviceKitLogo.svg` — ServerKit's geometry (white inset tile
34+
`x/y=150, 1748², rx=150`; gradient squircle ring; glyph in `brandGradient
35+
#6366f1 → #4f46e5` diagonal) with the server shelves replaced by a **phone**: rounded
36+
body, white screen cutout, speaker slot, home indicator, and the green `#3ddc97` status
37+
LED kept for family continuity. Starting draft (refine proportions during execution):
38+
39+
```xml
40+
<?xml version="1.0" encoding="UTF-8"?>
41+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
42+
<defs>
43+
<linearGradient id="brandGradient" x1="0%" y1="0%" x2="100%" y2="100%">
44+
<stop offset="0%" stop-color="#6366f1"/>
45+
<stop offset="100%" stop-color="#4f46e5"/>
46+
</linearGradient>
47+
</defs>
48+
<rect x="150" y="150" width="1748" height="1748" rx="150" fill="white"/>
49+
<!-- outer squircle ring -->
50+
<path fill="url(#brandGradient)" fill-rule="evenodd" d="M378,70 H1670 A308,308 0 0 1 1978,378 V1670 A308,308 0 0 1 1670,1978 H378 A308,308 0 0 1 70,1670 V378 A308,308 0 0 1 378,70 Z M450,220 A230,230 0 0 0 220,450 V1598 A230,230 0 0 0 450,1828 H1598 A230,230 0 0 0 1828,1598 V450 A230,230 0 0 0 1598,220 Z"/>
51+
<!-- phone body with screen cutout -->
52+
<path fill="url(#brandGradient)" fill-rule="evenodd" d="M804,434 H1244 A100,100 0 0 1 1344,534 V1514 A100,100 0 0 1 1244,1614 H804 A100,100 0 0 1 704,1514 V534 A100,100 0 0 1 804,434 Z M794,560 A30,30 0 0 0 764,590 V1390 A30,30 0 0 0 794,1420 H1254 A30,30 0 0 0 1284,1390 V590 A30,30 0 0 0 1254,560 Z"/>
53+
<rect x="944" y="481" width="160" height="32" rx="16" fill="white"/>
54+
<rect x="924" y="1501" width="200" height="32" rx="16" fill="white"/>
55+
<circle cx="1216" cy="628" r="28" fill="#3ddc97"/>
56+
</svg>
57+
```
58+
59+
- `frontend/src/components/Logo.jsx` — same paths, gradient stops wired to the accent ramp
60+
so the mark follows the user's accent. **Gotcha:** plan 12 stores accent vars as
61+
space-separated `R G B` triplets for Tailwind alpha, so stops must be
62+
`stopColor="rgb(var(--accent-hover, 90 103 232))"` etc., and the gradient id must come
63+
from `useId()` (ServerKit's collision lesson). Props: `size`, `className`.
64+
65+
### Part 2 — Web rollout
66+
67+
- Sidebar header (`App.jsx`) and Login/invite card (`auth/Login.jsx`): swap the white
68+
`Layers` square for `<Logo size={…}/>`. About pane gets a 64px one.
69+
- **Favicon:** replace `frontend/public/favicon.svg` with a 32×32 stroke redraw in
70+
ServerKit's favicon style — gradient tile `#6d7cff → #5a67e8`, white 2px stroke phone,
71+
green `#3ddc97` LED:
72+
73+
```xml
74+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
75+
<defs>
76+
<linearGradient id="dkFavTile" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
77+
<stop stop-color="#6d7cff"/><stop offset="1" stop-color="#5a67e8"/>
78+
</linearGradient>
79+
</defs>
80+
<rect width="32" height="32" rx="7" fill="url(#dkFavTile)"/>
81+
<g fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
82+
<rect x="10.5" y="5" width="11" height="22" rx="2.5"/>
83+
<line x1="14.5" y1="23.5" x2="17.5" y2="23.5"/>
84+
</g>
85+
<circle cx="23" cy="8" r="2.5" fill="#3ddc97"/>
86+
</svg>
87+
```
88+
89+
- `index.html`: icon links (svg + ico/png rasters if practical), title stays `DeviceKit`.
90+
Add `public/manifest.json` (name `DeviceKit`, `theme_color #4f46e5`,
91+
`background_color #000000`, icon `/favicon.svg` maskable) if absent.
92+
93+
### Part 3 — Brand color: purple becomes the default accent
94+
95+
- `theme.js`: `DEFAULT_ACCENT` `#10b981` → **`#6d7cff`** (ServerKit's live periwinkle;
96+
the static logo asset keeps the deeper `#6366f1/#4f46e5` pair — same two-purples split
97+
ServerKit uses). Add Periwinkle/Indigo to `ACCENT_PRESETS`; update the
98+
`appearance.accent` default in `SETTINGS_DEFAULTS`.
99+
- **Semantic emerald is untouched** — plan 12's rule stands: online/pass/success stays
100+
`#10b981`; only brand chrome rides the accent ramp. Users with a stored accent keep it
101+
(settings row wins); only the default changes.
102+
103+
### Part 4 — Android agent alignment
104+
105+
- `ic_launcher_background.xml`: solid `#6366F1` → the diagonal gradient (VectorDrawable
106+
`<gradient>` inside the adaptive background, API 26+ — already the minimum for
107+
`mipmap-anydpi-v26`). Align `ic_launcher_foreground.xml`'s phone glyph with the master's
108+
proportions; touch `ic_notification.xml` if it drifts. App label stays `DeviceKit Agent`.
109+
- Rebuild via the `build-agent-apk` skill; distribute through plan 25's OTA channel once
110+
live (until then, hand-flash — same as every agent change today).
111+
112+
## Phases
113+
114+
| Phase | Delivers | Proves |
115+
|---|---|---|
116+
| 1 | Master SVG + `Logo.jsx` + sidebar/login/About swap + favicon + manifest | one mark everywhere on web; changing the accent re-tints the sidebar logo live |
117+
| 2 | Purple default accent (`#6d7cff`), presets updated, settings default | a fresh install boots purple; an existing user's chosen accent survives |
118+
| 3 | Android adaptive-icon gradient + glyph alignment + APK rebuild | the launcher icon on a real device matches the web favicon family |
119+
120+
## Decisions to make while executing (log, don't stop)
121+
122+
- **Sidebar tile treatment:** full `<Logo/>` mark vs ServerKit's sidebar pattern (lucide
123+
glyph in a small accent-gradient tile). Recommend the real `<Logo/>` at 26–32px — DeviceKit
124+
finally has a mark; use it.
125+
- **Raster favicons** (`.ico`, 16/32 png): generate if a quick tool is at hand
126+
(sharp/cairosvg one-liner); the SVG link alone covers modern browsers — don't block on it.
127+
- **`index.html` title** currently `DeviceKit Pro | SamanLabs` — normalize to `DeviceKit`
128+
(PageTitle already overrides at runtime).
129+
130+
## Definition of done
131+
132+
Sidebar, login, About, browser tab, and the Android launcher all show the same purple
133+
phone-in-squircle family; the web mark re-tints when the accent changes; no `Layers`-as-logo
134+
usage remains (grep `Layers` in App.jsx/Login.jsx); phase 3's APK on a physical device shows
135+
the gradient icon.
136+
137+
## Out of scope
138+
139+
- **Light/system theme + white-label** — plan 28 (white-label will let users *replace* this
140+
logo; it needs `Logo.jsx` to exist first).
141+
- **Desktop/tray icon pipeline** (ServerKit's `generate_icons.py`) — DeviceKit has no desktop
142+
agent; revisit if one appears.
143+
144+
## ServerKit source map (for implementers)
145+
146+
- Master SVG: `frontend/src/assets/ServerKitLogo.svg` (geometry to clone)
147+
- JSX twin + useId gotcha: `frontend/src/components/ServerKitLogo.jsx`
148+
- Favicon style: `frontend/public/favicon.svg`; manifest: `frontend/public/manifest.json`
149+
- Sidebar tile alternative: `components/Sidebar.jsx:325-330` + `styles/components/_sidebar.scss:179`
150+
- Icon rasterization reference: `serverkit-agent/packaging/icons/generate_icons.py`

0 commit comments

Comments
 (0)