-
+
+
+
+
+
+ Docs
+
+
+
+ New tenant
+
+
+
-
- O.3 scaffolding
- Operator portal · placeholder
-
- You're signed in via the dezky-operator Authentik client. Real screens
- (Overview, Tenants, Partners, Infrastructure, etc.) land in O.4 once the design system
- is ported. This page exists to prove the OAuth round-trip works and to smoke-test the
- operator-only endpoints on platform-api.
-
-
-
- Smoke test · POST /partners
-
- Calls https://api.dezky.local/partners through a server-side proxy that
- forwards your access token. With an operator-scoped token this should return 200 +
- the created partner; with a customer-portal token (try in the other app) it returns 403.
-
-
- {{ smokeBusy ? 'Calling…' : 'Create partner "test-partner"' }}
-
+
+
+
+
+
Smoke test · POST /partners
+
+ Forwards your access token to platform-api. Operator-scoped tokens succeed
+ (200 first time, 409 thereafter). Customer-portal tokens return 403.
+
+
+
+ {{ smokeBusy ? 'Calling…' : 'Create partner' }}
+
+
{{ smokeResult }}
-
+
-
-
+
+ Session
+
+
+
diff --git a/docs/OPERATOR-PLAN.md b/docs/OPERATOR-PLAN.md
index afd6724..d42ea53 100644
--- a/docs/OPERATOR-PLAN.md
+++ b/docs/OPERATOR-PLAN.md
@@ -337,17 +337,37 @@ done in order — earlier ones unblock later ones.
`iss` URL); `AUTHENTIK_ISSUER` env is now comma-separated. The audience
change in O.2 wasn't enough on its own — issuer matching is separate
-### O.4 · Design system + app shell
+### O.4 · Design system + app shell ✓
-- [ ] `assets/styles/tokens.css` — copy with `data-theme="dark"` as default
-- [ ] `assets/styles/base.css`
-- [ ] Components: `NodeMark.vue`, `UiIcon.vue` (copy from portal)
-- [ ] Shared primitives ported from the design: `Card`, `Button`, `Table`,
- `Badge`, `Mono`, `Eyebrow`, `StatusDot`, `Avatar`, `PageHeader`
-- [ ] `OpSidebar.vue` — collapsible, badges per nav item
-- [ ] `OpTopbar.vue` — env badge, ⌘K trigger, on-call pill, bell, avatar
-- [ ] `app.vue` shell wires sidebar + topbar + `
`
-- [ ] Keyboard shortcut: ⌘[ collapses sidebar, ⌘K opens palette
+- [x] `assets/styles/tokens.css` carbon-default (done in O.3)
+- [x] `assets/styles/base.css` (done in O.3)
+- [x] `NodeMark.vue` (copied unchanged from portal),
+ `UiIcon.vue` (expanded set: 31 icons covering sidebar/topbar/sort/arrows)
+- [x] Shared primitives: `Card`, `UiButton` (5 variants × 3 sizes),
+ `DataTable`, `Badge` (7 tones), `Mono`, `Eyebrow`, `StatusDot`,
+ `Avatar` (deterministic palette), `PageHeader`
+- [x] `OpSidebar.vue` — collapsible (232↔56px), 12 nav items in 4
+ sections, active-row highlight from route, badge slot per item,
+ brand mark + user identity footer
+- [x] `OpTopbar.vue` — env badge (prod/staging/dev), ⌘K palette trigger
+ stub, on-call pill, bell, avatar
+- [x] `layouts/default.vue` wires sidebar + topbar + `
`;
+ `layouts/blank.vue` for the login page; `app.vue` uses `
`
+- [x] Keyboard shortcut: ⌘[ collapses/expands sidebar (verified — width
+ flips 232↔56 in the browser via the toggle click). ⌘K palette lands
+ in O.8
+- [x] Verified in browser: shell renders with all 12 nav links, env badge
+ shows PROD, PageHeader title resolves to the user's display name,
+ smoke test re-confirmed 409 on the seeded `test-partner` (token
+ forwarding still works after the layout refactor)
+
+### Gotcha worth noting
+
+- Vite 7.3 added a strict `server.allowedHosts` check that blocks any
+ Host header that isn't an exact match for the dev origin. The customer
+ portal was scaffolded under an older Vite and pre-dates this. Operator
+ needs `allowedHosts: ['operator.dezky.local']` in `nuxt.config.ts`
+ under `vite.server` or every request 403s with a plaintext error.
### O.5 · Tenant management (real backend)