@the-portland-company/shell 0.76.18 → 0.78.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/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.78.0
2
+
3
+ Dashboard, Battleground and District Data are three features in two Modes; no Polls row in the Relationship left bar. Specs cited: Reference/UI-Platform "Dashboard, Battleground and District Data are three features in two Modes" (decided 2026-10-12, Ben), Specs/Left-Navigation §9.1 and §9.2.
4
+
5
+ - **New registry rows `battleground` and `districts`**, both `mode: 'campaign'`, titles "Battleground" and "District Data". The Dashboard deployment must render those two pages with `app="battleground"` / `app="districts"`, never `app="dashboard"`: the `dashboard` row is unscoped, so it draws the Relationship rail, which is how both pages showed inside Relationship Mode with a "Dashboard · Battleground · District Data" strip above the header (measured 2026-10-12). Their hosts are decided (`battleground.politogyvrm.com`, `districts.politogyvrm.com`; UI-Platform, 2026-10-12) but not live, so both origins and both left-bar links stay on the dashboard host until each goes live.
6
+ - **`dashboard` row unchanged** in behavior; its comment now says it is the Relationship Dashboard only.
7
+
8
+ Polls: Spec cited: Specs/Left-Navigation §9.1 ("Polls is not a Relationship row. Polling is a Campaign Mode feature only").
9
+
10
+ - **Relationship tree.** `standardPolitogyNav` no longer adds a `Polls` row to the Collect section (it was restored in 0.75.0). Collect is Calls to action, Forms, Surveys. Seen live on dashboard.politogyvrm.com in Relationship Mode on 2026-10-12.
11
+ - **Campaign tree unchanged.** `Polling` stays under Research, and the `polls` registry entry is already `mode: 'campaign'`, so the Polls app keeps an active row.
12
+ - **For consumers.** `hiddenItems: ['Polls']` is now a no-op and can be dropped.
13
+ - Tests: `__tests__/standardNav.test.ts`.
14
+
15
+ ## 0.77.0
16
+
17
+ No theme control in the account menu. Specs cited: Reference/UI-Platform "Theme — light only (Dark Mode retired)" and "There is no theme control … Apps must not render any theme control"; Reference/Global-Rules "Theming — Light Only".
18
+
19
+ - **Breaking for the eye only.** `PolitogyAppFrame` no longer passes `theme` to the account menu, so the "Appearance" radio group (one option, "Light", since 0.73.0 made the theme light only) is gone from every app. `NavUserMenu.theme` stays optional for a caller that still passes it; nothing in the shell does.
20
+ - `./theme-preference` and `useThemePreference` are unchanged: they still resolve every stored preference to light and keep the cross-app cookie contract for the apps that import them.
21
+ - **Audit note (closeout 2026-10-12, item 1).** The "Known shell defect — dark theme restored in 0.68.0" paragraph in Reference/UI-Platform is stale: 0.73.0 (#116, 2026-10-06) removed dark rendering, `chrome.css` ships no `.dark` block (the remaining mentions are comments), and the pre-paint script hardcodes light. The runtime Brand token sheet (specs app, `lib/brand-authoring.ts`) still emits `.dark {}` blocks that nothing applies.
22
+
1
23
  ## 0.76.18
2
24
 
3
25
  Consolidates 0.76.16 and 0.76.17, neither of which reached npm (their publish gates failed). Specs cited: Global-Rules v38, UI-Platform v46, Specs/Left-Navigation (no Tribes row, §9.1), Specs/System-Communications §6.3 and §12.2 (Binding, 2026-10-03).
@@ -487,92 +487,6 @@ function notificationsFeedToPayload(feed, orgId) {
487
487
  badge: feed.badge
488
488
  };
489
489
  }
490
-
491
- // src/theme-preference.ts
492
- var THEME_STORAGE_KEY = "politogy_theme";
493
- var THEME_COOKIE = "politogy_theme";
494
- var THEME_PENDING_COOKIE = "politogy_theme_pending";
495
- var VALID2 = ["light", "dark", "system"];
496
- function isThemePreference(v) {
497
- return typeof v === "string" && VALID2.includes(v);
498
- }
499
- function readThemeCookie() {
500
- if (typeof document === "undefined") return null;
501
- try {
502
- const m = document.cookie.match(
503
- new RegExp(`(?:^|;\\s*)${THEME_COOKIE}=([^;]+)`)
504
- );
505
- const v = m ? decodeURIComponent(m[1]) : null;
506
- return isThemePreference(v) ? v : null;
507
- } catch {
508
- return null;
509
- }
510
- }
511
- function writeThemeCookie(pref) {
512
- if (typeof document === "undefined" || typeof window === "undefined") return;
513
- const host = window.location.hostname;
514
- const onParentDomain = /(^|\.)politogyvrm\.com$/i.test(host);
515
- const domainAttr = onParentDomain ? "; Domain=.politogyvrm.com" : "";
516
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
517
- document.cookie = `${THEME_COOKIE}=${encodeURIComponent(
518
- pref
519
- )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
520
- }
521
- function writeSharedCookie(name, value, maxAgeSeconds) {
522
- if (typeof document === "undefined" || typeof window === "undefined") return;
523
- const host = window.location.hostname;
524
- const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
525
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
526
- document.cookie = `${name}=${encodeURIComponent(
527
- value
528
- )}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${domainAttr}${secure}`;
529
- }
530
- function markThemeChoicePending(pref) {
531
- writeSharedCookie(THEME_PENDING_COOKIE, pref, 31536e3);
532
- }
533
- function readPendingThemeChoice() {
534
- if (typeof document === "undefined") return null;
535
- try {
536
- const m = document.cookie.match(
537
- new RegExp(`(?:^|;\\s*)${THEME_PENDING_COOKIE}=([^;]+)`)
538
- );
539
- const v = m ? decodeURIComponent(m[1]) : null;
540
- return isThemePreference(v) ? v : null;
541
- } catch {
542
- return null;
543
- }
544
- }
545
- function clearPendingThemeChoice() {
546
- writeSharedCookie(THEME_PENDING_COOKIE, "", 0);
547
- }
548
- function resolveTheme(_pref) {
549
- return "light";
550
- }
551
- function applyThemePreference(pref) {
552
- if (typeof document === "undefined") return;
553
- const resolved = resolveTheme();
554
- const d = document.documentElement;
555
- d.classList.toggle("dark", resolved === "dark");
556
- try {
557
- d.setAttribute("data-theme", resolved);
558
- d.style.colorScheme = resolved;
559
- } catch {
560
- }
561
- }
562
- `(function(){try{
563
- var KEY=${JSON.stringify(THEME_STORAGE_KEY)};
564
- var VALID=["light","dark","system"];
565
- function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${THEME_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
566
- var cookie=fromCookie();
567
- var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
568
- var pref=cookie||ls||"system";
569
- if(VALID.indexOf(pref)===-1)pref="system";
570
- if(cookie&&cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}
571
- var dark=false;
572
- var d=document.documentElement;
573
- if(dark)d.classList.add("dark");else d.classList.remove("dark");
574
- try{d.setAttribute("data-theme",dark?"dark":"light");d.style.colorScheme=dark?"dark":"light";}catch(e){}
575
- }catch(e){}})();`;
576
490
  var CORE_APP_ORIGIN = "https://app.politogyvrm.com";
577
491
  var CORE_BILLING_PATH = "/organization-settings/subscription-billing/text-email";
578
492
  var CORE_BILLING_URL = `${CORE_APP_ORIGIN}${CORE_BILLING_PATH}`;
@@ -2201,102 +2115,6 @@ var NAV_DISPLAY_OPTIONS = [
2201
2115
  { value: "tabs", label: "Tabs Only" },
2202
2116
  { value: "both", label: "Both" }
2203
2117
  ];
2204
- function useThemePreference() {
2205
- const ctx = react.useContext(NavChromeContext);
2206
- const fetchJson = ctx?.fetchJson ?? null;
2207
- const [value, setValueState] = react.useState("system");
2208
- const reconciled = react.useRef(false);
2209
- react.useEffect(() => {
2210
- const cookie = readThemeCookie();
2211
- let ls = null;
2212
- try {
2213
- ls = localStorage.getItem(THEME_STORAGE_KEY);
2214
- } catch {
2215
- }
2216
- const pref = cookie ?? (isThemePreference(ls) ? ls : null) ?? "system";
2217
- setValueState(pref);
2218
- applyThemePreference();
2219
- }, []);
2220
- const meCtx = react.useContext(NavChromeContext);
2221
- react.useEffect(() => {
2222
- if (reconciled.current) return;
2223
- const pending = readPendingThemeChoice();
2224
- if (pending) {
2225
- if (meCtx?.me) reconciled.current = true;
2226
- setValueState(pending);
2227
- applyThemePreference();
2228
- writeThemeCookie(pending);
2229
- try {
2230
- localStorage.setItem(THEME_STORAGE_KEY, pending);
2231
- } catch {
2232
- }
2233
- if (!meCtx?.me) return;
2234
- fetchJson?.("me/preferences", {
2235
- method: "PATCH",
2236
- headers: { "Content-Type": "application/json" },
2237
- body: JSON.stringify({ themePreference: pending })
2238
- }).then(() => clearPendingThemeChoice()).catch(() => {
2239
- });
2240
- return;
2241
- }
2242
- const pref = meCtx?.me?.themePreference;
2243
- if (isThemePreference(pref)) {
2244
- reconciled.current = true;
2245
- setValueState(pref);
2246
- applyThemePreference();
2247
- writeThemeCookie(pref);
2248
- try {
2249
- localStorage.setItem(THEME_STORAGE_KEY, pref);
2250
- } catch {
2251
- }
2252
- }
2253
- }, [meCtx?.me]);
2254
- react.useEffect(() => {
2255
- const onStorage = (e) => {
2256
- if (e.key === THEME_STORAGE_KEY && isThemePreference(e.newValue)) {
2257
- setValueState(e.newValue);
2258
- applyThemePreference(e.newValue);
2259
- }
2260
- };
2261
- const onWake = () => {
2262
- const cookie = readThemeCookie();
2263
- if (cookie) {
2264
- setValueState((prev) => {
2265
- if (cookie !== prev) applyThemePreference();
2266
- return cookie;
2267
- });
2268
- }
2269
- };
2270
- window.addEventListener("storage", onStorage);
2271
- window.addEventListener("focus", onWake);
2272
- document.addEventListener("visibilitychange", onWake);
2273
- return () => {
2274
- window.removeEventListener("storage", onStorage);
2275
- window.removeEventListener("focus", onWake);
2276
- document.removeEventListener("visibilitychange", onWake);
2277
- };
2278
- }, []);
2279
- const setValue = react.useCallback(
2280
- (next) => {
2281
- setValueState(next);
2282
- applyThemePreference();
2283
- writeThemeCookie(next);
2284
- try {
2285
- localStorage.setItem(THEME_STORAGE_KEY, next);
2286
- } catch {
2287
- }
2288
- markThemeChoicePending(next);
2289
- fetchJson?.("me/preferences", {
2290
- method: "PATCH",
2291
- headers: { "Content-Type": "application/json" },
2292
- body: JSON.stringify({ themePreference: next })
2293
- }).then(() => clearPendingThemeChoice()).catch(() => {
2294
- });
2295
- },
2296
- [fetchJson]
2297
- );
2298
- return { value, setValue };
2299
- }
2300
2118
  var standardNavIcons = {
2301
2119
  home: lu.LuHouse,
2302
2120
  users: lu.LuUsers,
@@ -2543,16 +2361,9 @@ function standardPolitogyNav(opts) {
2543
2361
  href: withOrg("https://surveys.politogyvrm.com/surveys"),
2544
2362
  children: APP_SURVEYS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2545
2363
  },
2546
- // Polls (polls.politogyvrm.com) — kept in the default Collect section;
2547
- // apps that want it hidden pass `hiddenItems: ['Polls']`. Regression:
2548
- // dropped entirely in 03c4dc2 when app.politogyvrm.com retired; restored
2549
- // here (this row is independent of the retired app).
2550
- {
2551
- label: "Polls",
2552
- icon: icon("chartNoAxes"),
2553
- href: withOrg(POLLS_ORIGIN),
2554
- children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2555
- },
2364
+ // No Polls row here: Polling is a Campaign Mode feature only
2365
+ // (Specs/Left-Navigation §9.1 "Polls is not a Relationship row"). Its
2366
+ // row is "Polling" under Research in the Campaign tree below.
2556
2367
  ...accountRows()
2557
2368
  ];
2558
2369
  };
@@ -2566,11 +2377,14 @@ function standardPolitogyNav(opts) {
2566
2377
  // Battleground is the Campaign landing page; there is no Campaign
2567
2378
  // Dashboard row, and Voter Contact and Tribes have no row.
2568
2379
  { kind: "section", label: "Race data" },
2569
- // Battleground moved to the Dashboard app (dashboard.politogyvrm.com)
2570
- // until a dedicated Battleground host is decided (UI-Platform route table).
2380
+ // Battleground is its own Campaign Mode app (registry key
2381
+ // `battleground`), not part of the Dashboard. It is served from the
2382
+ // dashboard deployment only until battleground.politogyvrm.com is live
2383
+ // (Reference/UI-Platform, decided 2026-10-12).
2571
2384
  { label: "Battleground", icon: icon("crosshair"), href: withOrg(`${DASHBOARD_ORIGIN}battleground`) },
2572
- // District data moved to the Dashboard app (dashboard.politogyvrm.com)
2573
- // per Reference/UI-Platform v44.
2385
+ // District Data is its own Campaign Mode app (registry key
2386
+ // `districts`); served from the dashboard deployment until
2387
+ // districts.politogyvrm.com is live.
2574
2388
  { label: "District data", icon: icon("landmark"), href: withOrg(`${DASHBOARD_ORIGIN}districts`) },
2575
2389
  { kind: "section", label: "Research" },
2576
2390
  {
@@ -4731,11 +4545,23 @@ var POLITOGY_APPS = {
4731
4545
  polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls", mode: "campaign", grants: ["title"] },
4732
4546
  ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas", grants: ["title"] },
4733
4547
  // V2 apps that replace retired app.politogyvrm.com pages (Reference/
4734
- // UI-Platform v42 "Retirement of app.politogyvrm.com"). Dashboard hosts the
4735
- // Relationship Dashboard + Campaign Battleground/District data/Campaigns
4736
- // (Specs/Relationship-Dashboard); Settings hosts org settings & billing
4737
- // (Specs/Organization-Settings).
4548
+ // UI-Platform v42 "Retirement of app.politogyvrm.com"). Dashboard is the
4549
+ // Relationship Dashboard, the Relationship Mode landing page, and nothing
4550
+ // else (Specs/Relationship-Dashboard §1A "Relationship Mode only"); Settings
4551
+ // hosts org settings & billing (Specs/Organization-Settings).
4738
4552
  dashboard: { title: "Dashboard", origin: "https://dashboard.politogyvrm.com", storagePrefix: "dashboard" },
4553
+ // Battleground (the Campaign Mode landing page) and District Data are
4554
+ // Campaign Mode features, each its own app (Specs/Left-Navigation §9.2;
4555
+ // Reference/UI-Platform, decided 2026-10-12). They are never rendered with
4556
+ // `app="dashboard"`: that frame is the Relationship rail, which is how they
4557
+ // came to show inside Relationship Mode. Their hosts are decided
4558
+ // (UI-Platform, 2026-10-12): battleground.politogyvrm.com and
4559
+ // districts.politogyvrm.com. Neither is live yet, so the pages are still
4560
+ // served from the dashboard deployment and the origin below is that host.
4561
+ // When a host goes live, change its origin here and its row's href in
4562
+ // standardNav in the same release.
4563
+ battleground: { title: "Battleground", origin: "https://dashboard.politogyvrm.com", storagePrefix: "battleground", mode: "campaign" },
4564
+ districts: { title: "District Data", origin: "https://dashboard.politogyvrm.com", storagePrefix: "districts", mode: "campaign" },
4739
4565
  settings: { title: "Settings", origin: "https://settings.politogyvrm.com", storagePrefix: "settings" },
4740
4566
  petitions: {
4741
4567
  title: "Petitions",
@@ -5597,7 +5423,6 @@ function useChromeUserMenu({
5597
5423
  email
5598
5424
  }) {
5599
5425
  const state = useChromeState();
5600
- const theme = useThemePreference();
5601
5426
  const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null;
5602
5427
  const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : "";
5603
5428
  const coreBase = getCoreBaseUrl();
@@ -5608,7 +5433,9 @@ function useChromeUserMenu({
5608
5433
  roles: (state?.orgs ?? []).filter((o) => o.rawRole || o.role).map((o) => ({ role: o.rawRole ?? o.role, orgName: o.name })),
5609
5434
  accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],
5610
5435
  addAccountHref: `${coreBase}/my-profile#accounts`,
5611
- theme: { value: theme.value, onChange: theme.setValue },
5436
+ // No theme control: the product is light only (Reference/UI-Platform
5437
+ // "Theme — light only"; shell 0.73.0 stopped applying dark). The account
5438
+ // menu rendered an "Appearance" radio with one option until 0.77.0.
5612
5439
  navDisplay: state ? { value: state.navDisplay, onChange: state.setNavDisplay } : void 0
5613
5440
  };
5614
5441
  }