From c71e782dc01aca322141bdf2289eb88445dc5010 Mon Sep 17 00:00:00 2001 From: Ronni Baslund Date: Sun, 24 May 2026 08:34:34 +0200 Subject: [PATCH] feat(operator): command palette, impersonation, incident, tweaks (O.8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CommandPalette + useCommandPalette: ⌘K opens a search-and-jump panel over real tenants/partners + fixture flags + nav + actions. Arrow keys + Enter navigate, Escape/backdrop close. Recents are intentionally omitted for now; add when there's something to recent over. - Impersonation stub: useImpersonation + ImpersonationModal + ImpersonationBanner. Modal opens from tenant detail and from the palette. Banner stays at the top of the shell until exited. No real OBO token is minted — wiring OAuth Token Exchange is tracked as a follow-up. - IncidentModal + useIncidentModal: opened from the Overview and Infrastructure incident banners, renders the mock INCIDENT data with metrics, timeline and draft composer. - TweaksPanel + useTweaks: floating bottom-right panel for theme (dark/light), density (comfy/compact), env badge (prod/staging/dev). Saved to localStorage. - Theme/density apply via [data-theme] + [data-density] overrides in tokens.css. Topbar env badge now reads from useTweaks instead of a prop. - Layout wires ⌘K + ⌘[ at the document level and mounts the palette + modals + banner + tweaks panel once for all pages. --- apps/operator/assets/styles/tokens.css | 43 ++ apps/operator/components/CommandPalette.vue | 366 ++++++++++++++++++ .../components/ImpersonationBanner.vue | 86 ++++ .../components/ImpersonationModal.vue | 158 ++++++++ apps/operator/components/IncidentModal.vue | 167 ++++++++ apps/operator/components/OpTopbar.vue | 15 +- apps/operator/components/TweaksPanel.vue | 152 ++++++++ .../operator/composables/useCommandPalette.ts | 19 + apps/operator/composables/useImpersonation.ts | 34 ++ apps/operator/composables/useIncidentModal.ts | 11 + apps/operator/composables/useTweaks.ts | 69 ++++ apps/operator/layouts/default.vue | 32 +- apps/operator/pages/index.vue | 4 +- apps/operator/pages/infrastructure.vue | 3 +- apps/operator/pages/tenants/[slug].vue | 6 + docs/OPERATOR-PLAN.md | 27 +- 16 files changed, 1162 insertions(+), 30 deletions(-) create mode 100644 apps/operator/components/CommandPalette.vue create mode 100644 apps/operator/components/ImpersonationBanner.vue create mode 100644 apps/operator/components/ImpersonationModal.vue create mode 100644 apps/operator/components/IncidentModal.vue create mode 100644 apps/operator/components/TweaksPanel.vue create mode 100644 apps/operator/composables/useCommandPalette.ts create mode 100644 apps/operator/composables/useImpersonation.ts create mode 100644 apps/operator/composables/useIncidentModal.ts create mode 100644 apps/operator/composables/useTweaks.ts diff --git a/apps/operator/assets/styles/tokens.css b/apps/operator/assets/styles/tokens.css index bb40023..0da2f4c 100644 --- a/apps/operator/assets/styles/tokens.css +++ b/apps/operator/assets/styles/tokens.css @@ -37,4 +37,47 @@ --font-mono: 'JetBrains Mono', ui-monospace, 'Menlo', monospace; --input-bg: rgba(244, 243, 238, 0.04); + + /* Cosmetic density. comfy=1, compact≈0.78. Used by page chrome that opts in + (PageHeader / table cells). Reading via calc() keeps layouts in one place. */ + --density-scale: 1; +} + +/* Tweaks: density overrides */ +:root[data-density='compact'] { --density-scale: 0.78; } + +/* Tweaks: light theme (warm cream, charcoal text). Overrides every surface + token so any component that uses var(--bg / --surface / --text / ...) flips + without code changes. Components that hard-code rgba(244,243,238,...) will + not flip — those should switch to tokens if they care about light mode. */ +:root[data-theme='light'] { + --bg: #F6F4EF; + --surface: #FAF8F2; + --elevated: #FFFFFF; + --border: #E2DED2; + --border-hi: #D0CBBC; + + --text: #1C1B17; + --text-dim: rgba(28, 27, 23, 0.72); + --text-mute: rgba(28, 27, 23, 0.50); + + --side-bg: #F0EDE4; + --side-surf: #FAF8F2; + --side-border: #E2DED2; + --side-text: #1C1B17; + --side-dim: rgba(28, 27, 23, 0.62); + --side-mute: rgba(28, 27, 23, 0.42); + --side-hover: rgba(28, 27, 23, 0.05); + --side-active: rgba(28, 27, 23, 0.08); + + --accent: #1F8A5B; + --accent-fg: #FAF8F2; + --signal: #1F8A5B; + + --ok: #1F8A5B; + --warn: #C97F1F; + --bad: #C03A3A; + --info: #2A6FDB; + + --input-bg: rgba(28, 27, 23, 0.04); } diff --git a/apps/operator/components/CommandPalette.vue b/apps/operator/components/CommandPalette.vue new file mode 100644 index 0000000..c777043 --- /dev/null +++ b/apps/operator/components/CommandPalette.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/apps/operator/components/ImpersonationBanner.vue b/apps/operator/components/ImpersonationBanner.vue new file mode 100644 index 0000000..4a58d24 --- /dev/null +++ b/apps/operator/components/ImpersonationBanner.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/apps/operator/components/ImpersonationModal.vue b/apps/operator/components/ImpersonationModal.vue new file mode 100644 index 0000000..82d7994 --- /dev/null +++ b/apps/operator/components/ImpersonationModal.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/apps/operator/components/IncidentModal.vue b/apps/operator/components/IncidentModal.vue new file mode 100644 index 0000000..f6b43ed --- /dev/null +++ b/apps/operator/components/IncidentModal.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/apps/operator/components/OpTopbar.vue b/apps/operator/components/OpTopbar.vue index d2701ed..06d6082 100644 --- a/apps/operator/components/OpTopbar.vue +++ b/apps/operator/components/OpTopbar.vue @@ -1,12 +1,9 @@