@the-portland-company/shell 0.64.0 → 0.65.1
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/app-chrome.cjs +289 -256
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.js +3 -3
- package/dist/app-chrome.js.map +1 -1
- package/dist/chrome.css +22 -95
- package/dist/{chunk-U4RZPZET.js → chunk-JHIFJZ73.js} +9 -16
- package/dist/chunk-JHIFJZ73.js.map +1 -0
- package/dist/{chunk-LFLN5GD6.js → chunk-NRKRUM5B.js} +278 -238
- package/dist/chunk-NRKRUM5B.js.map +1 -0
- package/dist/index.cjs +5 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/native.cjs +288 -255
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +21 -1
- package/dist/native.d.ts +21 -1
- package/dist/native.js +2 -2
- package/dist/theme-preference.cjs +7 -14
- package/dist/theme-preference.cjs.map +1 -1
- package/dist/theme-preference.d.cts +3 -1
- package/dist/theme-preference.d.ts +3 -1
- package/dist/theme-preference.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LFLN5GD6.js.map +0 -1
- package/dist/chunk-U4RZPZET.js.map +0 -1
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
--
|
|
389
|
-
--
|
|
390
|
-
--
|
|
391
|
-
--
|
|
392
|
-
--
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
|
@@ -56,14 +56,7 @@ function clearPendingThemeChoice() {
|
|
|
56
56
|
writeSharedCookie(THEME_PENDING_COOKIE, "", 0);
|
|
57
57
|
}
|
|
58
58
|
function resolveTheme(pref) {
|
|
59
|
-
|
|
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(
|
|
76
|
+
const resolved = resolveTheme();
|
|
84
77
|
const d = document.documentElement;
|
|
85
|
-
d.classList.
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
try{d.style.colorScheme=
|
|
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-
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
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"]}
|