@the-portland-company/shell 0.47.0-rc.3 → 0.47.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/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## 0.47.1 — 2026-08-13
2
+
3
+ Two fixes found by running a consuming app's WCAG contrast gate against 0.47.0.
4
+ Both are 0.47.0 defects; nothing from before 0.47.0 is affected.
5
+
6
+ ### Fixed — `--color-danger` collided with apps' own Tailwind theme colour
7
+
8
+ 0.47.0 introduced `--color-danger` at `:root` to separate "attention/error" from
9
+ "accent". The name was a bad choice: Tailwind v4 derives utilities from
10
+ `--color-*` theme keys, and Petitions already defined
11
+ `--color-danger: var(--tone-danger)` (`#c53030`) to back its `bg-danger` badges.
12
+ The shell's declaration won the cascade, so every `bg-danger` element repainted
13
+ from `#c53030` to `#E63946` — dropping white-on-danger from 6.2:1 to **4.17:1**
14
+ and failing WCAG AA on eight badges in one view.
15
+
16
+ Renamed to **`--chrome-danger`**. The shell should not squat a generic
17
+ `--color-*` name that apps legitimately own; `--color-accent` stays as it is,
18
+ because that one is a documented chrome alias apps already consume.
19
+
20
+ ### Fixed — a Mode accent must not carry into dark
21
+
22
+ 0.47.0 let a Mode's accent through to dark on the theory that a foreground
23
+ colour is safe there. Measured: Petition Mode's `#15803D` on the dark elevated
24
+ panel `#1F1F1F` is **3.29:1**, and it regressed fourteen routes at once.
25
+
26
+ A Mode's palette is authored against white. Nothing makes it legible against
27
+ black, and deriving a dark variant would be inventing a dark theme for a Mode
28
+ whose design never had one — which the brief explicitly ruled out. So a Mode
29
+ accent is now reset under `.dark`, and dark keeps the platform's neutral ramp
30
+ exactly as it did on 0.46.0. This is chrome POLICY, in `chrome.css`, so no Mode
31
+ can opt out of it by authoring a value.
32
+
1
33
  ## 0.47.0 — 2026-08-13
2
34
 
3
35
  Petition Mode has been green since V1 and has rendered red for the whole of the
@@ -20,7 +52,7 @@ Besides mis-signalling, it made the accent unoverridable — apps reasonably set
20
52
 
21
53
  Those 32 references now resolve a semantic token, split by what the colour
22
54
  actually means: `--color-accent` for genuine accent furniture, and a new
23
- `--color-danger` for attention and error (count badges, the low-fuel meter and
55
+ `--chrome-danger` for attention and error (count badges, the low-fuel meter and
24
56
  its nudge, validation messages). The danger set stays red in every Mode — a
25
57
  green "3 unread" badge is worse than a red one.
26
58
 
@@ -64,9 +96,25 @@ Dark mode keeps the platform's neutral ramp. The Brand sheet resets the Mode
64
96
  SURFACES to the guaranteed-invalid value under `.dark` so they fall through to
65
97
  the neutrals; only the accent, a foreground colour, carries over.
66
98
 
67
- Active sub-rows also gain a 3px left rail drawn from `--nav-row-active-rail`,
68
- which resolves `transparent` unless the Mode declared a chrome group — so for
69
- every other app the span renders invisible and out of flow.
99
+ Active sub-rows also gain a 3px left rail drawn from
100
+ `var(--mode-accent, transparent)` — invisible and out of flow for any app whose
101
+ Mode declares no chrome group.
102
+
103
+ **Where Mode SURFACES are read matters, and the rule is not obvious.** They are
104
+ resolved at the point of use with the pre-0.47 value as an inline fallback:
105
+
106
+ ```
107
+ bg-[color:var(--mode-accent-surface,var(--panel-elevated))]
108
+ ```
109
+
110
+ Not through an alias declared in the `[data-politogy-mode]` block — that block
111
+ does not match an app which is not mode-scoped, so the alias would be unset and
112
+ a bare `var()` is invalid at computed-value time, dropping the property to its
113
+ initial value. And not through a `:root` alias either — `.dark` is a separate
114
+ top-level selector, so a `:root` alias substitutes the LIGHT value and inherits
115
+ it into dark. Point of use is the only placement correct in both themes, scoped
116
+ and unscoped. Two tests enforce it; both mistakes were made and caught during
117
+ the release candidates.
70
118
 
71
119
  Needs the matching Brand API release (`tokens.modes.<id>.chrome`).
72
120
 
@@ -108,6 +156,19 @@ a silent effect.
108
156
 
109
157
  A grant is for a fact only the app HAS, never for a look.
110
158
 
159
+ ### Added — the brand lockup follows the Mode
160
+
161
+ `brandLogoSrc` became core-only in 0.43, so every mode-scoped app rendered the
162
+ Relationship lockup — a red wordmark on a purple `vrm` badge — whatever Mode it
163
+ was scoped to. Petition Mode shipped a green Petition lockup in V1 and lost it in
164
+ that migration.
165
+
166
+ Resolved from the registry by Mode, like the accent:
167
+ `MODE_BRAND_LOGOS = { petition: '/brand/logos/petition-mode-logo.svg' }`. Only
168
+ Modes with a shipped lockup are listed; everything else keeps today's default.
169
+ The path is app-relative, so add the asset to the app's `public/` before adding
170
+ a Mode here.
171
+
111
172
  ### Changed — prereleases publish under the `next` dist-tag
112
173
 
113
174
  `npm publish` tags `latest` whatever the semver, so an rc would silently become
@@ -165,7 +165,7 @@ function resolveOrgLogoUrl(url) {
165
165
  return `${CORE_APP_ORIGIN}${raw.startsWith("/") ? "" : "/"}${raw}`;
166
166
  }
167
167
  function topBarIconButtonClass(tone = "default") {
168
- const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-danger)] hover:text-[color:var(--color-danger)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
168
+ const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--chrome-danger)] hover:text-[color:var(--chrome-danger)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
169
169
  return [
170
170
  // `group/tbtip` powers the shared <TopBarTooltip> rendered inside the trigger.
171
171
  "group/tbtip relative flex items-center justify-center rounded-full border border-[color:var(--line)] transition",
@@ -192,7 +192,7 @@ function useAnyTopBarPanelOpen() {
192
192
  }
193
193
  function TopBarCountBadge({ count }) {
194
194
  if (!(count > 0)) return null;
195
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-danger)] px-1 text-[10px] font-normal leading-none text-white", children: count > 99 ? "99+" : count });
195
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--chrome-danger)] px-1 text-[10px] font-normal leading-none text-white", children: count > 99 ? "99+" : count });
196
196
  }
197
197
  function TopBarTooltip({
198
198
  title,
@@ -516,7 +516,7 @@ function FuelRow({
516
516
  {
517
517
  className: [
518
518
  "text-xs font-semibold tabular-nums",
519
- low ? "text-[color:var(--color-danger)]" : "text-[color:var(--muted)]"
519
+ low ? "text-[color:var(--chrome-danger)]" : "text-[color:var(--muted)]"
520
520
  ].join(" "),
521
521
  children: [
522
522
  remaining.toLocaleString(),
@@ -540,7 +540,7 @@ function FuelRow({
540
540
  {
541
541
  className: [
542
542
  "h-full rounded-full transition-[width]",
543
- low ? "bg-[color:var(--color-danger)]" : "bg-[color:var(--color-ok,#00A651)]"
543
+ low ? "bg-[color:var(--chrome-danger)]" : "bg-[color:var(--color-ok,#00A651)]"
544
544
  ].join(" "),
545
545
  style: { width: `${ratio}%` }
546
546
  }
@@ -576,14 +576,14 @@ function LowFuelHint({ lowFuel, orgId }) {
576
576
  "div",
577
577
  {
578
578
  role: "status",
579
- className: "absolute right-0 top-[calc(100%+10px)] z-50 w-60 rounded-lg border border-[color:var(--color-danger)] bg-[color:var(--color-danger)] px-3 py-2 text-white shadow-[var(--shadow-soft)]",
579
+ className: "absolute right-0 top-[calc(100%+10px)] z-50 w-60 rounded-lg border border-[color:var(--chrome-danger)] bg-[color:var(--chrome-danger)] px-3 py-2 text-white shadow-[var(--shadow-soft)]",
580
580
  onClick: (e) => e.stopPropagation(),
581
581
  children: [
582
582
  /* @__PURE__ */ jsxRuntime.jsx(
583
583
  "span",
584
584
  {
585
585
  "aria-hidden": true,
586
- className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 bg-[color:var(--color-danger)]"
586
+ className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 bg-[color:var(--chrome-danger)]"
587
587
  }
588
588
  ),
589
589
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
@@ -1237,7 +1237,7 @@ function SupportAction({ orgId } = {}) {
1237
1237
  className: "w-full rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-accent)]"
1238
1238
  }
1239
1239
  ),
1240
- showEmailError ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-[color:var(--color-danger)]", children: "Enter an email so support can reply." }) : null
1240
+ showEmailError ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-[color:var(--chrome-danger)]", children: "Enter an email so support can reply." }) : null
1241
1241
  ] }) : null,
1242
1242
  missingPhone ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-3 block", children: [
1243
1243
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block text-xs font-semibold text-[color:var(--foreground)]", children: "Your phone \u2014 optional" }),
@@ -1262,7 +1262,7 @@ function SupportAction({ orgId } = {}) {
1262
1262
  className: "mt-4 w-full resize-y rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-accent)]"
1263
1263
  }
1264
1264
  ),
1265
- state === "error" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-[color:var(--color-danger)]", children: "Sending failed \u2014 try again." }) : null,
1265
+ state === "error" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-[color:var(--chrome-danger)]", children: "Sending failed \u2014 try again." }) : null,
1266
1266
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
1267
1267
  /* @__PURE__ */ jsxRuntime.jsx(
1268
1268
  "button",