@the-portland-company/shell 0.75.0 → 0.75.2

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,20 @@
1
+ ## 0.75.2
2
+
3
+ Two fixes found on top of 0.75.1:
4
+
5
+ - **Contrast (item 4).** Several rail/mono text colors were tuned only against the ink rail (`#1B1C20`) and dropped below WCAG 2.2 AA's 4.5:1 minimum for normal-size text on the Campaign-mode red rail (`#8F1F2A`, raised card `#A12430`) and the loading screen (`#0a0a0c`): the account-menu "Roles & Organizations" caption (`--muted` / `--topbar-breadcrumb-color`, 3.63:1 on `--panel-elevated`), the sidebar user-email/mode caption (`--sidebar-caption`, 3.25:1 on the Campaign rail), the org-id hash (2.93:1 on the Campaign card), the nav section eyebrow (`--sidebar-eyebrow`, 3.55:1 even on the ink rail), and the loading-screen footer tagline (inline literal, 3.50:1). Fixed entirely through tokens: `--topbar-breadcrumb-color` darkened to `#5C6B80`; `--sidebar-text-muted`/`--sidebar-meta-text` lightened to `#CDCFD2`; `--sidebar-eyebrow`/`--sidebar-caption`/`--sidebar-row-text` raised to `rgba(255,255,255,.72)`/`.66`; the loading-screen's two `rgba(255,255,255,0.38)` literals (footer tagline and the error-state "Sign out" button) raised to `0.72`; and `native/index.tsx`'s hardcoded `--sidebar-meta-text` fallback updated to match. Each value clears 4.5:1 against every background it can actually render on (ink rail, Campaign rail, Campaign card, loading-screen ink, white/`--panel-elevated`), not just the one it was first tuned against. New test: `__tests__/sidebarContrast.test.ts` computes WCAG relative-luminance contrast for each token/background pair and fails the build if any drops below 4.5:1.
6
+ - **Missing `title` grant (item 5).** `POLITOGY_APPS.forms` had no `title` grant, so Forms could never publish a real per-page title — the page header fell back to the static registry title "Forms" forever and the designed "mode" eyebrow + H2 + subtitle block never rendered with real page data. Same gap existed on every other feature app with real per-page names: `surveys`, `polls`, `ctas`, `email`, `a2p`, `phonebank`, `canvass`, `signs` all got `grants: ['title']` added, by the same reasoning already applied to `petitions`. Left `dashboard` and `settings` (no per-record pages), `contacts` (already `pageTitle: false`), and `vrm` (core, gated by `app === CORE_APP` instead) ungranted. `__tests__/frame-grants.test.tsx` rewritten to assert the full grant set explicitly.
7
+
8
+ **Noted, not fixed:** UI-Platform (v44) says the `breadcrumbs` grant was retired 2026-09-30, but `petitions` still carries `grants: ['breadcrumbs', 'title']` and the `PolitogyFrameGrant` type still includes `'breadcrumbs'`. Removing it touches `deriveBreadcrumbs`, the `NavBreadcrumb` type, `PolitogyGrantedFrame.breadcrumbs`/`PolitogyCoreFrame.page.breadcrumbs`, and the `breadcrumbs` prop threaded into `NavTopBar` — out of scope for this release. Flagging per spec-is-source-of-truth rather than doing an unscoped removal.
9
+
10
+ ## 0.75.1
11
+
12
+ Three visual-regression fixes found by cross-app comparison (every app diverged from the design the same three ways):
13
+
14
+ - **Font tokens.** `--font-sans`, `--font-mono` and `--font-plex-mono` were referenced across `loading-screen.tsx` and `native/index.tsx` (and `theme/index.ts` read `--type-mono-family`) but `chrome.css` never emitted any of them, so those surfaces silently fell back to a hardcoded system stack instead of the Brand `--font-family` token — the actual source of the Inter/Acumin mismatch. Every sans surface now consumes `--font-family` (the one token the Brand API emits); every mono surface now consumes one shared, shell-emitted `--font-mono` (also added to `scripts/gen-tokens.mjs`).
15
+ - **Card tokens.** `--color-panel` aliased `--color-surface` (`--surface-1`, #F0F4F7 — the PAGE background), so every card rendered page-tinted instead of the Brand Card white. `--color-border` aliased `--line` / `--sidebar-border`, a token scoped to the always-dark left rail, leaking a dark-rail-intended border onto light content. Fixed in `chrome.css` (and `gen-tokens.mjs`): `--color-panel` is now `--color-layer-3` (white); `--color-border` is now `--color-g3` (#D7DADF, the neutral Brand border step).
16
+ - **Mode eyebrow + tab icons.** `PageHeader` had no `mode`/`eyebrow` prop at all, so the 11px uppercase mode eyebrow (Reference/UI-Platform → "Page header") never had anywhere to render; it now renders by default whenever `mode` is passed (`showEyebrow`/`eyebrow` to override). `NavTab.icon` was marked `@deprecated` and explicitly not rendered as of 0.69.0 — reversed by the 2026-10-06 Tabs decision ("Icon + label. Each tab is a Lucide icon") — `NavTabStrip` now renders the icon again, 15px (13px on `size="sm"`), 1.75 stroke, 6px left of the label, following the label's color via `currentColor`.
17
+
1
18
  ## 0.75.0
2
19
 
3
20
  Built on top of 0.74.0 (`claude/retire-nav`). Two fixes:
@@ -2896,7 +2896,7 @@ function NavSidebar({
2896
2896
  "div",
2897
2897
  {
2898
2898
  "data-shell-meta": true,
2899
- className: `mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[11px] text-[color:var(--sidebar-meta-text,rgba(255,255,255,0.4))] ${rc("max-md:hidden")}`,
2899
+ className: `mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[11px] text-[color:var(--sidebar-meta-text,rgba(255,255,255,0.72))] ${rc("max-md:hidden")}`,
2900
2900
  children: [
2901
2901
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: [
2902
2902
  footer.envLabel ? shortEnvLabel(footer.envLabel) : null,
@@ -3046,7 +3046,7 @@ function NavActiveRail() {
3046
3046
  function NavRowBadge({ value }) {
3047
3047
  const rc = useRailCollapse();
3048
3048
  if (value === void 0 || value === null || value === "") return null;
3049
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `ml-auto shrink-0 font-[family-name:var(--font-plex-mono,ui-monospace,monospace)] text-[11px] font-semibold leading-none text-[color:var(--sidebar-caption,var(--sidebar-text-muted,var(--muted)))] ${rc("max-md:hidden")}`, children: value });
3049
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `ml-auto shrink-0 font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[11px] font-semibold leading-none text-[color:var(--sidebar-caption,var(--sidebar-text-muted,var(--muted)))] ${rc("max-md:hidden")}`, children: value });
3050
3050
  }
3051
3051
  function NavSectionRow({
3052
3052
  label,
@@ -3160,7 +3160,7 @@ function OrgIdCopyButton({ id }) {
3160
3160
  setTimeout(() => setCopied(false), 1500);
3161
3161
  },
3162
3162
  title: "Copy organization id",
3163
- className: "-ml-1 rounded px-1 text-left font-[family-name:var(--font-plex-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--sidebar-caption,var(--sidebar-text-muted,var(--muted)))] hover:bg-[color:var(--sidebar-row-hover-bg,var(--panel))]",
3163
+ className: "-ml-1 rounded px-1 text-left font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--sidebar-caption,var(--sidebar-text-muted,var(--muted)))] hover:bg-[color:var(--sidebar-row-hover-bg,var(--panel))]",
3164
3164
  children: copied ? "Copied" : `\u2026${id.slice(-8)}`
3165
3165
  }
3166
3166
  );
@@ -3220,7 +3220,7 @@ function NavTopBar({
3220
3220
  "kbd",
3221
3221
  {
3222
3222
  "aria-hidden": true,
3223
- className: "pointer-events-none absolute right-[10px] hidden md:block rounded-[3px] border border-[color:var(--line)] px-[5px] py-px font-[family-name:var(--font-plex-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--muted)]",
3223
+ className: "pointer-events-none absolute right-[10px] hidden md:block rounded-[3px] border border-[color:var(--line)] px-[5px] py-px font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--muted)]",
3224
3224
  children: "/"
3225
3225
  }
3226
3226
  )
@@ -4243,7 +4243,7 @@ function LoadingScreen({
4243
4243
  display: "flex",
4244
4244
  alignItems: "center",
4245
4245
  gap: "0.6rem",
4246
- fontFamily: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)"
4246
+ fontFamily: "var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif)"
4247
4247
  },
4248
4248
  children: [
4249
4249
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4337,7 +4337,7 @@ function LoadingScreen({
4337
4337
  border: "none",
4338
4338
  background: "var(--color-race-red, #dc2626)",
4339
4339
  color: "#fff",
4340
- fontFamily: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)",
4340
+ fontFamily: "var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif)",
4341
4341
  fontSize: "0.875rem",
4342
4342
  fontWeight: 600,
4343
4343
  cursor: "pointer"
@@ -4355,8 +4355,10 @@ function LoadingScreen({
4355
4355
  padding: 0,
4356
4356
  border: "none",
4357
4357
  background: "transparent",
4358
- color: "rgba(255,255,255,0.38)",
4359
- fontFamily: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)",
4358
+ // 0.38 alpha was 3.51:1 on the ink-900 loading background —
4359
+ // below WCAG 2.2 AA 4.5:1. 0.72 clears it.
4360
+ color: "rgba(255,255,255,0.72)",
4361
+ fontFamily: "var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif)",
4360
4362
  fontSize: "12.5px",
4361
4363
  textDecoration: "underline",
4362
4364
  cursor: "pointer"
@@ -4380,9 +4382,11 @@ function LoadingScreen({
4380
4382
  alignItems: "center",
4381
4383
  justifyContent: "space-between",
4382
4384
  padding: "0 32px 30px",
4383
- fontFamily: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)",
4385
+ fontFamily: "var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif)",
4384
4386
  fontSize: "12.5px",
4385
- color: "rgba(255,255,255,0.38)"
4387
+ // 0.38 alpha was 3.51:1 on the ink-900 loading background — below
4388
+ // WCAG 2.2 AA 4.5:1. 0.72 clears it.
4389
+ color: "rgba(255,255,255,0.72)"
4386
4390
  },
4387
4391
  children: [
4388
4392
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Politogy VRM. Built for the long game." }),
@@ -4450,11 +4454,17 @@ var POLITOGY_APPS = {
4450
4454
  storagePrefix: "contacts",
4451
4455
  pageTitle: false
4452
4456
  },
4453
- forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms" },
4454
- surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys" },
4457
+ // 'title' for forms/surveys/polls/ctas for the same reason as petitions
4458
+ // below: their pages have real names ("Volunteer Signup", "Q3 Satisfaction"),
4459
+ // where the registry can only say the app's own name. Without the grant the
4460
+ // page header falls back to the static registry title forever, so the
4461
+ // designed "<mode> mode" eyebrow + H2 + subtitle block never gets the real
4462
+ // per-page title an app passes it (UI-Platform → Page header).
4463
+ forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms", grants: ["title"] },
4464
+ surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys", grants: ["title"] },
4455
4465
  // Specs/Left-Navigation §9.2: Polling is a Campaign row (Research).
4456
- polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls", mode: "campaign" },
4457
- ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas" },
4466
+ polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls", mode: "campaign", grants: ["title"] },
4467
+ ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas", grants: ["title"] },
4458
4468
  // V2 apps that replace retired app.politogyvrm.com pages (Reference/
4459
4469
  // UI-Platform v42 "Retirement of app.politogyvrm.com"). Dashboard hosts the
4460
4470
  // Relationship Dashboard + Campaign Battleground/District data/Campaigns
@@ -4478,7 +4488,7 @@ var POLITOGY_APPS = {
4478
4488
  },
4479
4489
  // Relationship Mode Email Engine (Specs/Email-Engine) — the V2 standalone
4480
4490
  // app, running in parallel to VRM core's V1 Email Broadcast.
4481
- email: { title: "Email", origin: "https://email.politogyvrm.com", storagePrefix: "email", mode: "relationship" },
4491
+ email: { title: "Email", origin: "https://email.politogyvrm.com", storagePrefix: "email", mode: "relationship", grants: ["title"] },
4482
4492
  // Campaign Mode apps — each scoped to Campaign, so they render the Campaign
4483
4493
  // tree and a static Campaign pill. Not yet deployed; hidden from the Campaign
4484
4494
  // nav by CAMPAIGN_FEATURE_AVAILABILITY until their launch flips the flag.
@@ -4491,11 +4501,12 @@ var POLITOGY_APPS = {
4491
4501
  origin: "https://a2p.politogyvrm.com",
4492
4502
  storagePrefix: "a2p",
4493
4503
  mode: "campaign",
4494
- dual: { modes: ["relationship", "campaign"], defaultMode: "campaign" }
4504
+ dual: { modes: ["relationship", "campaign"], defaultMode: "campaign" },
4505
+ grants: ["title"]
4495
4506
  },
4496
- phonebank: { title: "Phone Banking", origin: "https://phonebank.politogyvrm.com", storagePrefix: "phonebank", mode: "campaign" },
4497
- canvass: { title: "Field Canvassing", origin: "https://canvass.politogyvrm.com", storagePrefix: "canvass", mode: "campaign" },
4498
- signs: { title: "Sign Mapping", origin: "https://signs.politogyvrm.com", storagePrefix: "signs", mode: "campaign" }
4507
+ phonebank: { title: "Phone Banking", origin: "https://phonebank.politogyvrm.com", storagePrefix: "phonebank", mode: "campaign", grants: ["title"] },
4508
+ canvass: { title: "Field Canvassing", origin: "https://canvass.politogyvrm.com", storagePrefix: "canvass", mode: "campaign", grants: ["title"] },
4509
+ signs: { title: "Sign Mapping", origin: "https://signs.politogyvrm.com", storagePrefix: "signs", mode: "campaign", grants: ["title"] }
4499
4510
  };
4500
4511
  var DEFAULT_SEARCH_PLACEHOLDER = {
4501
4512
  contacts: "Search contacts, segments, forms"