@the-portland-company/shell 0.63.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/chrome.css CHANGED
@@ -373,105 +373,32 @@
373
373
  * their own and every existing `var(--color-accent)` read needs to follow. */
374
374
  }
375
375
 
376
- .dark {
377
- --background: 222.2 84% 4.9%;
378
- --foreground: 210 40% 98%;
379
- --card: 222.2 84% 4.9%;
380
- --card-foreground: 210 40% 98%;
381
- --popover: 222.2 84% 4.9%;
382
- --popover-foreground: 210 40% 98%;
383
- --primary: 210 40% 98%;
384
- --primary-foreground: 222.2 47.4% 11.2%;
385
- --secondary: 217.2 32.6% 17.5%;
386
- --secondary-foreground: 210 40% 98%;
387
- --muted: 217.2 32.6% 17.5%;
388
- --muted-foreground: 215 20.2% 65.1%;
389
- --accent: 217.2 32.6% 17.5%;
390
- --accent-foreground: 210 40% 98%;
391
- --destructive: 0 62.8% 30.6%;
392
- --destructive-foreground: 210 40% 98%;
393
- --border: 217.2 32.6% 17.5%;
394
- --input: 217.2 32.6% 17.5%;
395
- --ring: 212.7 26.8% 83.9%;
396
- --background: hsl(222.2 84% 4.9%);
397
- --foreground: #E8E8E8;
398
- --muted: #A3A3A3;
399
- --panel: #161616;
400
- --nav-surface: #161616;
401
- /* The --sidebar-* tokens the rail consumes as of 0.48 need their dark values
402
- HERE, not only in the runtime Brand sheet: stylesheet order between this
403
- file and the runtime link varies by bundler (Vite dev appends styles after
404
- the link), and a token whose dark override lives in only one of the two
405
- sheets flips with that order. Same neutral ramp as the Brand dark values. */
406
- --sidebar-bg: #161616;
407
- --sidebar-text: #E8E8E8;
408
- --sidebar-text-muted: #A3A3A3;
409
- --sidebar-meta-text: #A3A3A3;
410
- --panel-elevated: #1F1F1F;
411
- --line: #333333;
412
- --shadow-soft: 0 4px 16px rgba(0, 0, 0, 0.5);
413
- --color-bg: #0A0A0A;
414
- --color-surface: #161616;
415
- --color-surface-2: #1F1F1F;
416
- --color-panel: #161616;
417
- --color-panel-2: #1F1F1F;
418
- --color-border: #333333;
419
- --color-text: #E8E8E8;
420
- --color-text-muted: #A3A3A3;
421
- --color-muted: #A3A3A3;
422
- --skeleton-base: #212121;
423
- --skeleton-shine: #2B2B2B;
424
- --nav-submenu-bg: #212121;
425
- --nav-row-hover: #2B2B2B;
426
- --form-control-border: #333333;
427
- --form-control-border-hover: #404040;
428
- --form-control-focus: #A3A3A3;
429
- --form-placeholder-color: #8A8A8A;
430
- --form-value-color: #E8E8E8;
431
- --form-label-bg: #161616;
432
- --form-select-icon: #A3A3A3;
433
- --app-field-bg: #1F1F1F;
434
- --app-field-border: #333333;
435
- --app-field-border-hover: #404040;
436
- --app-floating-label-bg: #161616;
437
- --app-color-gray-50: #1F1F1F;
438
- --app-color-gray-100: #212121;
439
- --app-color-gray-200: #333333;
440
- --app-color-gray-300: #404040;
441
- --app-color-gray-400: #6B6B6B;
442
- --app-color-gray-500: #8A8A8A;
443
- --app-color-gray-600: #A3A3A3;
444
- --app-color-gray-700: #C6C6C6;
445
- --app-color-gray-800: #E8E8E8;
446
- --app-color-gray-900: #F5F5F5;
376
+ /* ---- Rail background is a fixed brand color, not a theme ----------------
377
+ * Reference/UI-Platform → Styling: the left rail is ink `#1B1C20` for
378
+ * Relationship and Petition, and `#8F1F2A` for Campaign — always, regardless
379
+ * of light/dark (dark mode is retired) and never a Tailwind/Chakra gray. It
380
+ * is NOT derived from `--mode-accent` (that stays reserved for the active-row
381
+ * highlight and hover gradient above); it is the rail's own surface, so it is
382
+ * keyed directly off the mode id the registry stamps.
383
+ *
384
+ * The Claude Design export for Campaign mode showed a lighter red
385
+ * (`--color-race-red` family) for the rail; the spec's fixed `#8F1F2A` wins
386
+ * per "stop and report the conflict" — noted here, not silently overridden. */
387
+ [data-politogy-mode] {
388
+ --sidebar-bg: #1B1C20;
389
+ --sidebar-text: #F5F5F5;
390
+ --sidebar-text-muted: #B5B7BB;
391
+ --sidebar-meta-text: #9A9CA0;
392
+ --sidebar-hover-bg: #2A2B30;
447
393
  }
448
394
 
449
- /* A Mode accent does not carry into dark. Dark keeps the platform's ramp.
450
- *
451
- * Measured, which is why this is a rule and not a preference: Petition Mode's
452
- * #15803D is a LIGHT-mode green. As a foreground on the dark elevated panel
453
- * (#1F1F1F) it is 3.29:1 — a WCAG AA failure, and it regressed 14 routes at
454
- * once when the accent was allowed through. A Mode's palette is authored
455
- * against white; nothing makes it legible against black, and deriving a dark
456
- * variant would be inventing a dark theme for a Mode whose design never had
457
- * one.
458
- *
459
- * So this is chrome POLICY, not token data: it lives here rather than in the
460
- * Brand API's dark block so that no Mode can opt out of it by authoring a
461
- * value. The Brand sheet separately blanks the Mode surfaces under `.dark`;
462
- * this covers the foregrounds.
463
- *
464
- * Higher specificity than the block above (0,2,0 vs 0,1,0), so it wins wherever
465
- * `.dark` is on an ancestor — which is where apps put it. */
466
- .dark [data-politogy-mode],
467
- [data-politogy-mode].dark {
468
- /* One declaration does it: `initial` is the guaranteed-invalid value, so
469
- every reader falls through to its own fallback — the aliases above go back
470
- to `--color-race-red`, and the active-row rail
471
- (`var(--mode-accent, transparent)`) disappears. */
472
- --mode-accent: initial;
395
+ [data-politogy-mode="campaign"] {
396
+ --sidebar-bg: #8F1F2A;
397
+ --sidebar-hover-bg: #7A1A23;
473
398
  }
474
399
 
400
+
401
+
475
402
  /* ---------------------------------------------------------------------------
476
403
  Third-party top-bar widget slots (<TopBarWidgetSlot>, e.g. DevNotes).
477
404
 
@@ -482,6 +409,7 @@
482
409
  win.
483
410
  --------------------------------------------------------------------------- */
484
411
  .topbar-widget-slot button {
412
+ position: relative !important;
485
413
  height: var(--topbar-icon-button-size, 32px) !important;
486
414
  min-height: var(--topbar-icon-button-size, 32px) !important;
487
415
  width: var(--topbar-icon-button-size, 32px) !important;
@@ -56,14 +56,7 @@ function clearPendingThemeChoice() {
56
56
  writeSharedCookie(THEME_PENDING_COOKIE, "", 0);
57
57
  }
58
58
  function resolveTheme(pref) {
59
- if (pref === "system") {
60
- try {
61
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
62
- } catch {
63
- return "light";
64
- }
65
- }
66
- return pref;
59
+ return "light";
67
60
  }
68
61
  function readStoredThemePreference() {
69
62
  const cookie = readThemeCookie();
@@ -80,9 +73,9 @@ function readResolvedTheme() {
80
73
  }
81
74
  function applyThemePreference(pref) {
82
75
  if (typeof document === "undefined") return;
83
- const resolved = resolveTheme(pref);
76
+ const resolved = resolveTheme();
84
77
  const d = document.documentElement;
85
- d.classList.toggle("dark", resolved === "dark");
78
+ d.classList.remove("dark");
86
79
  try {
87
80
  d.style.colorScheme = resolved;
88
81
  } catch {
@@ -97,13 +90,13 @@ var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
97
90
  var pref=cookie||ls||"system";
98
91
  if(VALID.indexOf(pref)===-1)pref="system";
99
92
  if(cookie&&cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}
100
- var resolved=pref;
101
- if(pref==="system"){try{resolved=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";}catch(e){resolved="light";}}
93
+ // Dark Mode retired platform-wide 2026-09-21 \u2014 always light, and strip any
94
+ // stale dark class a pre-retirement cookie/localStorage value left behind.
102
95
  var d=document.documentElement;
103
- if(resolved==="dark"){d.classList.add("dark");}else{d.classList.remove("dark");}
104
- try{d.style.colorScheme=resolved;}catch(e){}
96
+ d.classList.remove("dark");
97
+ try{d.style.colorScheme="light";}catch(e){}
105
98
  }catch(e){}})();`;
106
99
 
107
100
  export { THEME_COOKIE, THEME_PENDING_COOKIE, THEME_PREPAINT_SCRIPT, THEME_STORAGE_KEY, applyThemePreference, clearPendingThemeChoice, isThemePreference, markThemeChoicePending, readPendingThemeChoice, readResolvedTheme, readStoredThemePreference, readThemeCookie, resolveTheme, writeThemeCookie };
108
- //# sourceMappingURL=chunk-U4RZPZET.js.map
109
- //# sourceMappingURL=chunk-U4RZPZET.js.map
101
+ //# sourceMappingURL=chunk-JHIFJZ73.js.map
102
+ //# sourceMappingURL=chunk-JHIFJZ73.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme-preference.ts"],"names":[],"mappings":"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","file":"chunk-JHIFJZ73.js","sourcesContent":["// Universal theme preference — the ONE cross-app contract.\n//\n// One theme choice (Light / Dark / System) follows the authenticated user\n// across every Politogy app + subdomain. Three layers cooperate:\n// 1. VRM Core `profiles.theme_preference` — source of truth, persisted via\n// the chrome API (`PATCH me/preferences { themePreference }`) and read\n// back from `GET me` (`themePreference`).\n// 2. `.politogyvrm.com` cookie `politogy_theme` — zero-flash pre-paint\n// vehicle that crosses subdomains (auth session cookies are host-only).\n// 3. localStorage `politogy_theme` — offline fallback store.\n//\n// Ported verbatim from contacts/apps/web/src/lib/theme.ts (the origin of the\n// contract) so every app runs the identical behavior from npm. This module is\n// server-safe (no 'use client'): layouts import THEME_PREPAINT_SCRIPT for the\n// pre-paint inline script. The client-side hook lives in ./native\n// (useThemePreference).\n\nexport type ThemePreference = 'light' | 'dark' | 'system'\n\n// Single converged key for BOTH the localStorage store and the\n// cross-subdomain cookie, so a choice in one app is the same key in the next.\nexport const THEME_STORAGE_KEY = 'politogy_theme'\nexport const THEME_COOKIE = 'politogy_theme'\n\n// A local choice that has NOT yet reached VRM Core `profiles.theme_preference`.\n//\n// Politogy ID (auth.politogyvrm.com) has no chrome-API session to PATCH, so a\n// theme picked on the login screen only ever lands in the cookie. Without this\n// marker the next app booted the stale profile value through the reconcile step\n// below and overwrote the user's fresh choice — the \"I set System and it keeps\n// going back to Light\" bug (HITL 2026-07-23). The flag rides the same\n// cross-subdomain cookie so the first app that CAN talk to the chrome API\n// pushes the choice up instead of pulling the stale one down.\nexport const THEME_PENDING_COOKIE = 'politogy_theme_pending'\n\nconst VALID: readonly ThemePreference[] = ['light', 'dark', 'system']\n\nexport function isThemePreference(v: unknown): v is ThemePreference {\n return typeof v === 'string' && (VALID as readonly string[]).includes(v)\n}\n\n/** Read the shared cookie (browser only; null on the server or when unset). */\nexport function readThemeCookie(): ThemePreference | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${THEME_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isThemePreference(v) ? v : null\n } catch {\n return null\n }\n}\n\n// Set the cross-subdomain cookie. On a *.politogyvrm.com host we scope it to\n// the parent domain so every app subdomain reads it; on any other host\n// (localhost, *.pages.dev preview) we fall back to a host-only cookie so\n// local/preview theming still works without a mismatched Domain attribute\n// (which browsers silently drop). 1-year, SameSite=Lax, Secure on https.\nexport function writeThemeCookie(pref: ThemePreference): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n const host = window.location.hostname\n const onParentDomain = /(^|\\.)politogyvrm\\.com$/i.test(host)\n const domainAttr = onParentDomain ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n document.cookie = `${THEME_COOKIE}=${encodeURIComponent(\n pref,\n )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n}\n\n// Write an arbitrary cross-subdomain cookie with the same scoping rules as the\n// theme cookie itself (parent domain on *.politogyvrm.com, host-only elsewhere).\nfunction writeSharedCookie(name: string, value: string, maxAgeSeconds: number): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n const host = window.location.hostname\n const domainAttr = /(^|\\.)politogyvrm\\.com$/i.test(host) ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${domainAttr}${secure}`\n}\n\n/**\n * Mark the current cookie value as a local choice that still has to reach the\n * user's profile. Call this from any surface that changes the theme WITHOUT a\n * chrome-API session (Politogy ID's login screen is the only one today).\n */\nexport function markThemeChoicePending(pref: ThemePreference): void {\n writeSharedCookie(THEME_PENDING_COOKIE, pref, 31536000)\n}\n\n/** The pending (not-yet-persisted) local choice, if any. */\nexport function readPendingThemeChoice(): ThemePreference | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${THEME_PENDING_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isThemePreference(v) ? v : null\n } catch {\n return null\n }\n}\n\n/** Clear the pending marker once the choice has been written to the profile. */\nexport function clearPendingThemeChoice(): void {\n writeSharedCookie(THEME_PENDING_COOKIE, '', 0)\n}\n\n/** Resolve a preference to the concrete light/dark it renders as right now. */\nexport function resolveTheme(pref: ThemePreference): 'light' | 'dark' {\n // Dark Mode was retired platform-wide 2026-09-21 (Reference/UI-Platform →\n // Global Rules). `pref` is still read/stored so old cookies don't throw,\n // but every resolution is light now — no `.dark` class ever applies.\n void pref\n return 'light'\n}\n\n/**\n * Read the stored preference the way the pre-paint script does — cookie →\n * localStorage → `'system'`. Kept in lockstep with THEME_PREPAINT_SCRIPT so a\n * runtime reader can never disagree with what was applied before paint.\n */\nexport function readStoredThemePreference(): ThemePreference {\n const cookie = readThemeCookie()\n if (cookie) return cookie\n try {\n const stored = window.localStorage.getItem(THEME_STORAGE_KEY)\n if (isThemePreference(stored)) return stored\n } catch {\n /* localStorage may be unavailable (private mode) */\n }\n return 'system'\n}\n\n/**\n * The concrete light/dark the document is rendering as right now.\n *\n * This is the value anything theme-derived must follow — notably Chakra's\n * `colorMode`. Chakra used to be pinned to light while this resolved to dark,\n * which rendered dark chrome tokens over light Chakra content (the \"dark\n * labels on a light page\" desync).\n */\nexport function readResolvedTheme(): 'light' | 'dark' {\n return resolveTheme(readStoredThemePreference())\n}\n\n/** Apply a preference to the document. Dark Mode is retired — this always\n * resolves to light and makes sure no stale `.dark` class survives from a\n * pre-retirement cookie/localStorage value. */\nexport function applyThemePreference(pref: ThemePreference): void {\n if (typeof document === 'undefined') return\n const resolved = resolveTheme(pref)\n const d = document.documentElement\n d.classList.remove('dark')\n try {\n d.style.colorScheme = resolved\n } catch {\n /* non-fatal */\n }\n}\n\n// Inline pre-paint script — render it in <head> (or as the first child of\n// <body>) so the theme applies before first paint. Resolves the preference\n// from cookie → localStorage → OS, applies the `.dark` class + `color-scheme`,\n// and seeds localStorage from the cookie so the app hydrates to the synced\n// value (the cross-subdomain flash fix). Self-contained, dependency-free, and\n// wrapped so any failure is silent (never blocks paint).\nexport const THEME_PREPAINT_SCRIPT = `(function(){try{\nvar KEY=${JSON.stringify(THEME_STORAGE_KEY)};\nvar VALID=[\"light\",\"dark\",\"system\"];\nfunction fromCookie(){try{var m=document.cookie.match(/(?:^|;\\\\s*)${THEME_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}\nvar cookie=fromCookie();\nvar ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}\nvar pref=cookie||ls||\"system\";\nif(VALID.indexOf(pref)===-1)pref=\"system\";\nif(cookie&&cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}\n// Dark Mode retired platform-wide 2026-09-21 — always light, and strip any\n// stale dark class a pre-retirement cookie/localStorage value left behind.\nvar d=document.documentElement;\nd.classList.remove(\"dark\");\ntry{d.style.colorScheme=\"light\";}catch(e){}\n}catch(e){}})();`\n"]}