refactor(operator): derive env badge from hostname, not from user choice
A toggle-able env badge is a sticker, not a safety signal. Move env to
useEnv() which reads window.location.hostname:
*.local / localhost → 'dev'
*staging* → 'staging'
everything else → 'prod' (safest default)
- New composable: apps/operator/composables/useEnv.ts
- Topbar reads useEnv() instead of useTweaks().env
- useTweaks loses the env field; hydrate strips it from stale
localStorage payloads so old entries don't break
- TweaksPanel: env section removed (theme + density remain)
- Settings: env section removed from Appearance; added a read-only
Environment row to the Profile card showing the detected env +
hostname source ("auto-detected from operator.dezky.local")
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Detect which Dezky environment we're running against, from the browser
|
||||
// hostname. The env badge in the topbar reads this and uses it as a
|
||||
// "you're about to make changes in prod" warning — so it must NOT be
|
||||
// user-controllable.
|
||||
//
|
||||
// Rules:
|
||||
// - hostname ends in `.local` or is `localhost` → 'dev'
|
||||
// - hostname contains `staging` → 'staging'
|
||||
// - anything else → 'prod' (safest default — show the prod styling)
|
||||
//
|
||||
// SSR returns 'dev' by default because the server has no concept of the
|
||||
// browser hostname. The first client tick re-evaluates and the env pill
|
||||
// updates without a visible flash for the typical operator workflow
|
||||
// (signed-in client-side navigation).
|
||||
|
||||
export type Env = 'prod' | 'staging' | 'dev'
|
||||
|
||||
function detect(hostname: string): Env {
|
||||
const h = hostname.toLowerCase()
|
||||
if (h === 'localhost' || h === '127.0.0.1' || h.endsWith('.local')) return 'dev'
|
||||
if (h.includes('staging')) return 'staging'
|
||||
return 'prod'
|
||||
}
|
||||
|
||||
const current = ref<Env>('dev')
|
||||
const hostname = ref<string>('')
|
||||
let initialized = false
|
||||
|
||||
function init() {
|
||||
if (!import.meta.client || initialized) return
|
||||
hostname.value = window.location.hostname
|
||||
current.value = detect(hostname.value)
|
||||
initialized = true
|
||||
}
|
||||
|
||||
export const useEnv = () => {
|
||||
if (import.meta.client) init()
|
||||
return { env: current, hostname }
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
// Cosmetic tweaks for the operator shell — theme (dark/light), density
|
||||
// (comfy/compact), env badge (prod/staging/dev). Persisted in localStorage so
|
||||
// Cosmetic user-controllable preferences for the operator shell — theme
|
||||
// (dark/light) and density (comfy/compact). Persisted in localStorage so
|
||||
// the choices survive page reloads. The values are applied to <html> as
|
||||
// data-* attributes; tokens.css picks them up via selector overrides.
|
||||
//
|
||||
// NOTE: `env` used to live here but is now derived from the hostname via
|
||||
// `useEnv()` so it's a real environment signal, not a sticker the operator
|
||||
// can flip.
|
||||
|
||||
export type ThemeMode = 'dark' | 'light'
|
||||
export type Density = 'comfy' | 'compact'
|
||||
export type Env = 'prod' | 'staging' | 'dev'
|
||||
|
||||
interface TweakState {
|
||||
theme: ThemeMode
|
||||
density: Density
|
||||
env: Env
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'dezky-operator-tweaks'
|
||||
|
||||
const DEFAULTS: TweakState = { theme: 'dark', density: 'comfy', env: 'dev' }
|
||||
const DEFAULTS: TweakState = { theme: 'dark', density: 'comfy' }
|
||||
|
||||
const state = ref<TweakState>({ ...DEFAULTS })
|
||||
const hydrated = ref(false)
|
||||
@@ -41,8 +43,13 @@ function hydrate() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
// Old payloads carried an `env` field — pick fields we still care about
|
||||
// and ignore the rest so a stale localStorage entry doesn't break.
|
||||
const parsed = JSON.parse(raw) as Partial<TweakState>
|
||||
state.value = { ...DEFAULTS, ...parsed }
|
||||
state.value = {
|
||||
theme: parsed.theme ?? DEFAULTS.theme,
|
||||
density: parsed.density ?? DEFAULTS.density,
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore corrupt JSON
|
||||
@@ -64,6 +71,5 @@ export const useTweaks = () => {
|
||||
state,
|
||||
setTheme: (v: ThemeMode) => set('theme', v),
|
||||
setDensity: (v: Density) => set('density', v),
|
||||
setEnv: (v: Env) => set('env', v),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user