fold-ng 0.3.0 → 0.5.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
@@ -8,6 +8,378 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.5.1] - 2026-07-27
12
+
13
+ First published 0.5.x — same tree as the (unpublished, e2e-gated) v0.5.0 tag with the release e2e locator scoped to the first control.
14
+
15
+ ## [0.5.0] - 2026-07-27
16
+
17
+ ### Added
18
+
19
+ - **`fold-view-toggle` — a segmented single-select.** A compact Cards / Table
20
+ (or density, chart-mode…) switch, generic and zero-domain: pass `options`
21
+ (`{ value, icon?, label?, ariaLabel?, disabled? }`) and bind `[(value)]`. It's a
22
+ real `role="radiogroup"` of `role="radio"` segments — roving tabindex, arrow-key
23
+ selection, `Home`/`End`, disabled-skip — not two independent toggles. `size`
24
+ (`sm`/`md`), `iconOnly`, `activeStyle` (`raised` chip or `accent` brand tint),
25
+ `forced-colors`-aware. New gallery `/view-toggle` page.
26
+
27
+ - **`fold-password-field` + `revealable` on `fold-input`.** A password input with
28
+ a **live requirements checklist** — a dot per rule that turns on as the value
29
+ satisfies it — built in two layers. `fold-input` gains a reusable `revealable`
30
+ (a show/hide eye toggle on a `type="password"` input); `fold-password-field`
31
+ composes it with the checklist. Rules are **injected**, not hard-coded: a
32
+ `FoldPasswordRule` is `{ label, test }`, so a `RegExp` (`foldRegexRule`), a
33
+ `zod` `safeParse`, a length check or anything drops in — `rules` defaults to a
34
+ sensible policy (`foldDefaultPasswordRules`). `validChange` emits when every
35
+ rule passes; Signal-Forms-native; the checklist is an `aria-live` list that
36
+ labels each row met / not met. The checklist is **redesignable**: project into
37
+ the `[rules]` slot and drive your own markup off the exported live state
38
+ (`#pw="foldPasswordField"` → `pw.checklist()`), with the default list as the
39
+ fallback. The built-in row marker is a `dot` (filling in) or a `check` tick
40
+ (`marker="check"`). New gallery `/password` page.
41
+
42
+ - **`fold-popover` + `fold-dropdown` — anchored floating layer & actions menu.**
43
+ fold's first floating primitive. `fold-popover` renders projected content in
44
+ the native **top layer** (the `popover` attribute — escapes `overflow: hidden`
45
+ and every `z-index`), anchored to a projected `[foldPopoverTrigger]` by a
46
+ **dependency-free flip/shift engine** (`computePlacement`, exported and
47
+ unit-tested in isolation — no Floating UI). Controlled via `[(open)]`;
48
+ dismissal (outside-click + `Escape`) and focus-return are built in, and the
49
+ trigger gets `aria-haspopup`/`aria-expanded`/`aria-controls` wired
50
+ automatically. `fold-dropdown` (+ `fold-dropdown-item`) is the actions menu on
51
+ top: `role="menu"`, ↑/↓ roving tabindex, `Home`/`End`, type-ahead, opens onto
52
+ its first enabled item, closes returning focus to the trigger. New gallery
53
+ `/popover` page.
54
+
55
+ Hardened to a competitor benchmark (Floating UI / Radix): the placement engine
56
+ now does **flip → size → shift** — it picks the best-fitting side (preferred →
57
+ opposite → roomiest) and reports the available space so a tall panel gets a
58
+ `max-height` and **scrolls inside the viewport** instead of overflowing;
59
+ **autoUpdate** tracks the trigger + panel via `ResizeObserver` (not just
60
+ scroll/resize) so the anchor never drifts; an optional **`arrow`** points at
61
+ the trigger; **enter + exit transitions** are native CSS (`@starting-style` +
62
+ `transition-behavior: allow-discrete`, no JS timers); and the dropdown's
63
+ type-ahead is **multi-letter** (buffered); `fallbackPlacements` makes the flip
64
+ chain configurable; the `autoUpdate` helper is exported. Nested/sub-menus and
65
+ cursor-anchored context menus are tracked for a later pass.
66
+
67
+ Added a **Playwright interaction suite** (`pnpm test:e2e`, real Chromium) for
68
+ what jsdom can't reach — native top-layer open, box sizing, keyboard/focus.
69
+ It caught two focus bugs now fixed: the dropdown focused its first item
70
+ _before_ the popover had shown the panel (so keydowns missed the menu), and
71
+ focus-return targeted a non-focusable wrapper trigger (e.g. `fold-button-icon`)
72
+ instead of its inner control — the popover now resolves the focusable element
73
+ for both focus and the aria wiring.
74
+
75
+ - **`fold-listbox` (+ `fold-option`) — a styleable single-select.** The richer
76
+ sibling of `fold-select` (which wraps a native `<select>`): reach for it when
77
+ options need custom rendering the OS popup can't give — an icon, a second line,
78
+ a status dot. Built on `fold-popover`, so it inherits the native top layer,
79
+ flip/shift positioning, outside-click + `Escape` dismissal and focus return.
80
+ On top it implements the ARIA select pattern — a `role="listbox"` that holds
81
+ focus and drives `aria-activedescendant` — with full keyboard (↑/↓, `Home`/
82
+ `End`, multi-letter type-ahead, `Enter`), a disabled-row skip, and a pure-CSS
83
+ selected check. Signal-Forms-native (`FormValueControl<string>`, so `[formField]`
84
+ and `[(value)]` both work) and shares `fold-input`'s box chrome (sizes, `panel`
85
+ variant). Options are dumb + presentational — each derives its own selected /
86
+ active state from the parent by `computed`, so nothing is pushed in during
87
+ change detection. New gallery `/listbox` page and a Playwright suite. Option
88
+ groups and a filter/combobox variant are tracked for later.
89
+
90
+ - **`fold-multiselect` — the multi-select sibling.** Same styleable popover +
91
+ `fold-option` rows, but the value is a set (`readonly string[]`): activating a
92
+ row **toggles** its membership and the panel **stays open**. It's a separate
93
+ component, not a `multiple` flag, because the Signal-Forms value type genuinely
94
+ differs from single-select's `string` — keeping `[formField]` / `[(value)]`
95
+ honestly typed (no `any`). `role="listbox"` + `aria-multiselectable`, the same
96
+ keyboard core (`Enter`/`Space` toggle), each selected row keeps its check, and
97
+ the trigger summarises the picks. The keyboard/roving/type-ahead core and the
98
+ option↔owner contract are now **shared** between the two components (a
99
+ `FOLD_LISTBOX_OWNER` token instead of a concrete injection, which also removed
100
+ a circular import). New `/listbox` demo section + a Playwright suite.
101
+
102
+ - **`fold-inline-confirm` — in-place destructive-action guard.** Extracted from
103
+ SH3PHERD's shared inline-confirm (which replaced four ad-hoc patterns) and
104
+ rebuilt to fold conventions. The host projects a real focusable trigger
105
+ (`foldButton` / `fold-button-icon`); on activation it is swapped, in the same
106
+ slot, for a confirm/cancel row — no modal, no layout jump. Three families:
107
+ **simple** (`confirmed` emits `""`), **type-to-confirm** (`[match]` — the
108
+ button unlocks once the text matches, case-insensitive + trimmed), and
109
+ **secret** (`password` — a masked field that confirms when non-empty and emits
110
+ the typed value, since a password can only be verified server-side). `Escape`
111
+ cancels; `Enter` confirms. Fully i18n via `provideFoldInlineConfirmLabels()`
112
+ (English default) or a per-instance `labels` input. Composed of `fold-button`,
113
+ `fold-button-icon` and `fold-input`. New gallery `/inline-confirm` page.
114
+
115
+ Hardened against a competitor benchmark (Radix `AlertDialog` / React-Aria):
116
+ the warning `message` is wired to the confirm button via `aria-describedby`
117
+ (so it is announced on focus, not stranded on the group); focus reliably
118
+ returns to the trigger on close (the previous attempt read the trigger before
119
+ it re-rendered and no-op'd); the trigger no longer double-fires on `Enter`.
120
+ New API: `confirmIcon` (leading icon on the confirm button) and `cancelIcon`
121
+ now takes an **icon name of your choice** (was a fixed `×`); a two-way
122
+ `[(open)]` model plus `keepOpenOnConfirm` give a controlled async story —
123
+ keep the affordance open, show `loading`, close it when the request settles.
124
+ 16 spec blocks.
125
+
126
+ - **Slider hardcore pass — `fold-slider` + `fold-range-slider`.** `fold-slider`
127
+ now implements `FormValueControl<number>` (bind `[formField]`, or `[(value)]`);
128
+ the visible label is a real `<label for>` (else `ariaLabel`); a `valueText`
129
+ override is announced via `aria-valuetext`; `hint` + touched-gated `errors`.
130
+ `fold-range-slider` gets a `model()` value (`[(value)]` parity), `disabled`,
131
+ and i18n thumb labels (`minLabel` / `maxLabel`, English default) resolving the
132
+ hardcoded aria suffixes; it's a labelled `role="group"` with formatted
133
+ `aria-valuetext` per thumb (duration reads `mm:ss`). Both share a hardened
134
+ thumb — a focus-visible ring (was invisible on keyboard focus),
135
+ `prefers-reduced-motion`, `forced-colors`, and tokenised motion. New gallery
136
+ `/slider` page; README rows; specs 12 → 20.
137
+
138
+ - **`fold-paginator` hardcore pass.** Fully i18n — every string
139
+ (`Pagination`/prev/next/page/size/range/empty) is now overridable via
140
+ `provideFoldPaginatorLabels()` (English default) or a per-instance `labels`
141
+ input, resolving the last hardcoded-French portability blocker. Plus:
142
+ keyboard focus is preserved after a page change (moves to the active page, or
143
+ the prev/next arrow while it stays enabled — never dropped to `<body>`); the
144
+ visible range + active button clamp an out-of-range `currentPage` (a lagging
145
+ parent can't render a garbage range); the current `pageSize` is always in the
146
+ selector's options (the `<select>` can't show a phantom value); `disabled` is a
147
+ `booleanAttribute`; `siblingCount` is floored + zero-bounded. `@selector` + a
148
+ gallery `/paginator` page; specs cover DOM clicks, i18n, focus and the edges.
149
+
150
+ - **`fold-checkbox` — the boolean form control.** A native
151
+ `<input type="checkbox">` (keyboard, focus, the `checkbox` role, form
152
+ submission and `indeterminate` all native) visually replaced by a tokenised
153
+ box + check/dash mark. Signal-forms native via `FormCheckboxControl` (bind
154
+ `[formField]`), or standalone `[(checked)]`; plus `indeterminate`, `label` /
155
+ `ariaLabel`, `hint` + touched-gated `errors`, `required`, `size` (`sm`/`md`),
156
+ `disabled`. Accessible by construction (visible label wraps the control, or a
157
+ required `ariaLabel` — dev-warns when neither is set), with a focus-visible
158
+ ring, `prefers-reduced-motion` and `forced-colors` handled. `fold-data-table`'s
159
+ selection column now uses it.
160
+
161
+ - **`fold-data-table` — controlled row selection.** `selectable` renders a
162
+ checkbox column plus a header select-all with an indeterminate state over the
163
+ current rows; the parent owns the set via `selected` (a `Set` of row keys) and
164
+ `selectionChange` (emits the next set — the table never mutates). Selected rows
165
+ carry an accent tint + `aria-selected`, and a `selectionLabel` names each
166
+ checkbox. Toggling a checkbox never triggers `rowClick`.
167
+ - **`fold-data-table` — `mobileLayout` (parent owns the small-screen shape).**
168
+ `scroll` (default — stay tabular, scroll horizontally; the table imposes no
169
+ card), `auto-cards` (each row stacks into a label/value card), or `custom` —
170
+ the parent supplies `<ng-template foldRowCard let-row>` (new
171
+ `FoldDataTableRowCardDirective`) and the table renders _that_ per row on mobile
172
+ instead of an imposed card. The table owns the chrome, not the content.
173
+ - **`fold-data-table` — a `loading` state.** A fetching table now shows a
174
+ centred `fold-spinner` instead of the empty state, so an in-flight roster
175
+ reads as "loading", never as "no data".
176
+ - **`fold-data-table` — an optional toolbar/title bar.** Project content with
177
+ `[foldToolbar]` (a title, a live count, a bulk-action bar that appears once
178
+ rows are selected) and it renders as a visible band above the column header —
179
+ the same content-projection idiom as `fold-card`'s `[cardHeader]`, collapsing
180
+ to nothing when the parent projects nothing. It stays put while the body
181
+ scrolls (the table now has an inner scroll region). `toolbarSurface` lends the
182
+ table a level —
183
+ `default` / `sunken` / `raised` / `accent` — mapped only to fold surface
184
+ tokens (no hard-coded colour); `accent` reuses the shared
185
+ `[data-surface="accent"]` machinery, so the bar's content auto-inverts to the
186
+ on-accent palette per theme.
187
+ - **`fold-data-table` — an accessible `caption`.** A new `caption` input renders
188
+ a visually-hidden `<caption>` that names the table for assistive tech
189
+ (distinct from the visible `foldToolbar` title).
190
+ - **`fold-data-table` — `stickyFirst` + `density`.** `stickyFirst` pins the
191
+ checkbox + identity columns while the body scrolls horizontally (opaque-backed
192
+ so tints don't bleed); `density="compact"` tightens the row padding.
193
+ - **`fold-data-table` — column `align: "center"` and `truncate`.** `center`
194
+ joins `right`; `truncate` clips a column to one ellipsised line (pair with
195
+ `width`).
196
+ - **`data-table` gallery page.** Added `/data-table` to the demo (live sort, row
197
+ select, keyboard nav, the loading + empty states, a custom mobile card, and a
198
+ playground for every flag).
199
+ - **Select family — generic value + a data-driven `[options]` API.**
200
+ `fold-listbox` / `fold-multiselect` / `fold-option` are now generic over the
201
+ option value `T` (was `string`-only): `value` is `T | null` (single) /
202
+ `readonly T[]` (multi), and a `compareWith` input (default `Object.is`) matches
203
+ **object** values by identity — string/number/enum need nothing. On top of
204
+ projected `<fold-option>`, a `[options]="FoldSelectOption<T>[]"` array API links
205
+ the value type to the options at compile time (rich rows via a projected
206
+ `<ng-template #option let-o>`). The type stays honest end-to-end (`T` public,
207
+ the owner token erased to `unknown`, no `any`/`as`).
208
+ - **`fold-data-table` — i18n label token.** `provideFoldDataTableLabels` +
209
+ `FoldDataTableLabels` + a `labels` input (same shape as the paginator), so the
210
+ select-all/select-row/sort/loading accessible strings are overridable per
211
+ locale instead of hardcoded English.
212
+ - **`/changelog` — the CHANGELOG as a designed timeline.** A new gallery page
213
+ renders `CHANGELOG.md` as a vertical `fold-timeline` (one card per release,
214
+ category-count badges, breaking flagged) — parsed at build into a typed,
215
+ SSR-safe data file (runs pre-tokenised, no runtime markdown, no `innerHTML`).
216
+ - **`/lab` — an "in dev" index.** A dedicated menu of exactly the components not
217
+ yet on npm, each linking to its page with the version it ships in. Both the
218
+ list and the `dev` rail badges are derived from each nav item's `since` vs the
219
+ published version, so they clear themselves the moment a release is cut.
220
+ - **`pnpm eta` — a read-only release preview.** Prints the next version, the
221
+ derived bump level, and the reasons, straight from the CHANGELOG's
222
+ `[Unreleased]` section — no side effects.
223
+
224
+ ### Fixed
225
+
226
+ - **0.5 review-hardening pass** (multi-agent review of the release). `fold-data-table`
227
+ accessible strings are now i18n-overridable (were hardcoded); `fold-password-field`'s
228
+ requirements checklist actually **announces** rule flips (the `aria-live` region
229
+ now carries the met/not-met word as text, not just an attribute) and forwards
230
+ the reveal labels; a `forced-colors` + `prefers-reduced-motion` sweep across
231
+ `fold-data-table`, `fold-paginator`, the `fold-input` reveal button, `fold-view-toggle`
232
+ and the select-family trigger; `fold-view-toggle`'s roving tab stop never lands
233
+ on a disabled segment (+ a dev warning for a missing `ariaLabel`);
234
+ `fold-inline-confirm`'s Escape is guarded while `loading`; `fold-dropdown`
235
+ excludes Space from type-ahead; slider spacing tokenised.
236
+
237
+ - **Select family — a review-driven hardening pass** (`fold-listbox` /
238
+ `fold-multiselect` / `fold-popover`). Closing focus no longer traps: the
239
+ popover only pulls focus back to the trigger when it's still inside the closing
240
+ panel (Escape / pick) or nowhere — a `Tab` out now **advances** to the next
241
+ field like a native `<select>`. Dismissing the popup (Escape, outside-click,
242
+ Tab, or a pick) now marks the field **touched**, so a `required` select that's
243
+ opened and abandoned surfaces its error (blur parity). The trigger's
244
+ `aria-controls` points at the real `role="listbox"` (new `ariaControls` on
245
+ `fold-popover`), the active-row highlight and selected check get a
246
+ `forced-colors` treatment, and `fold-multiselect` membership is a `Set`
247
+ (O(1), not O(n²) across a long list) with the trigger summary collapsing to
248
+ "…, +N". Dev-mode now warns when a control holds a value with no matching
249
+ `<fold-option>`. New: `allowClear` on `fold-listbox` (a clear × once a value is
250
+ picked) and closed-trigger type-ahead (type to pick without opening).
251
+
252
+ - **`fold-popover` panels are now opaque.** The panel used `surface-raised` — a
253
+ ~5% tint meant to _sit on_ an opaque surface — so on the top layer the page
254
+ bled through (visible while scrolling a long `fold-listbox`). It now composites
255
+ that tint over an opaque `surface-card` base. The popover also publishes its
256
+ trigger width as `--fold-popover-anchor-width`, and `fold-listbox` reads it for
257
+ a `min-width` — so the panel is never narrower than the trigger (a coherent
258
+ select look).
259
+
260
+ - **Heading inputs no longer leak a native `title` tooltip.** A static
261
+ `title="…"` on a component that has a `title` input both seeds the input _and_
262
+ stays on the host as a real HTML attribute — so `fold-page-layout`,
263
+ `fold-page-section`, `fold-element-title`, `fold-context-card`,
264
+ `fold-empty-state` and `fold-panel-header` rendered their heading a second time
265
+ as a browser tooltip on hover. The reflected attribute is now stripped
266
+ (`host: { '[attr.title]': 'null' }`); `fold-icon` keeps its `title` on purpose
267
+ (it maps to `aria-label`).
268
+
269
+ ### Changed
270
+
271
+ - **Form-control box metrics are single-sourced** (`_field-box.scss`). The size
272
+ and `panel` dimensions shared by the native inputs (`input-shell`) and the
273
+ select-family triggers (`_listbox-shell`) now live in one Sass mixin, so a
274
+ metric redesign lands in both instead of drifting.
275
+
276
+ - **CI + release gates now run the Playwright browser tier.** The `test:e2e`
277
+ suite (native popover top layer, focus, positioning — behaviour jsdom can't
278
+ reach) is wired into both `ci.yml` (inside the single required `ci` job) and
279
+ `release.yml`, so a PR or a tag can't go green on a broken overlay.
280
+
281
+ - **The release bump is derived from the CHANGELOG.** `pnpm release` with no
282
+ argument now reads `[Unreleased]` and derives patch/minor/major (0.x-aware:
283
+ breaking → minor, features → minor, else patch) — the changelog you curate
284
+ defines the version. An explicit level still overrides. Parser + derivation
285
+ live in a shared `scripts/lib/changelog.mjs`, reused by the release flow, the
286
+ `pnpm eta` preview, and the `/changelog` page.
287
+
288
+ - **`fold-data-table` — clickable rows are a roving-tabindex group.** They now
289
+ answer Space as well as Enter (page-scroll suppressed) and Arrow Up/Down +
290
+ Home/End move focus between rows with a single tab stop — the ARIA grid
291
+ keyboard pattern, not a wall of tab stops.
292
+ - **`fold-data-table` — sort indicators are now `fold-icon`s** (`expand-all` when
293
+ idle, `chevron-up` / `chevron-down` when active) rather than text glyphs, so
294
+ the direction cue is theme-aware, pixel-aligned, and consistent with the rest
295
+ of the system. The icon is decorative — `aria-sort` on the `<th>` stays the
296
+ accessible carrier.
297
+ - Transitions now respect `prefers-reduced-motion`.
298
+ - **`fold-data-table` hardening (hardcore-review follow-ups).** Row keydown now
299
+ fires only when the row itself is focused — keys bubbling from an inner control
300
+ (a link, a button, the selection checkbox) are no longer stolen or
301
+ double-handled; the roving tab stop survives the focused row being removed
302
+ (never strands the group with zero tab stops); the `custom` mobile layout no
303
+ longer instantiates its card list on desktop; `truncate` warns in dev when its
304
+ column lacks a `width` to clip against; `aria-colcount` + a "Sort by …" label
305
+ on the sort control; the checkbox-column width is a single source of truth.
306
+
307
+ - **BREAKING (`fold-data-table`): the primary column renders as
308
+ `<th scope="row">`** (was a `<td>`) so screen readers announce each row by its
309
+ identity cell. Consumers that target the first cell with a `td`-specific
310
+ selector should switch to the `.folddt-cell` class (present on both the row
311
+ header and the data cells).
312
+ - **BREAKING (`fold-data-table`): `mobileCards` (boolean) is replaced by
313
+ `mobileLayout`, and the default flips from cards to scroll.** A narrow-screen
314
+ table now stays tabular (scrolls) unless asked to stack. `[mobileCards]="true"`
315
+ (or unset) → `mobileLayout="auto-cards"` to keep the stacked cards;
316
+ `[mobileCards]="false"` → drop it (`scroll` is the default).
317
+
318
+ ## [0.4.0] - 2026-07-25
319
+
320
+ ### Added
321
+
322
+ - **Auto-inverting accent surface.** A new `[data-surface="accent"]` region
323
+ (stamped by `fold-card surface="accent"`, `fold-hero-card surface="accent"`,
324
+ or the `foldSurface` directive) fills with the brand accent and re-points its
325
+ **whole content sub-tree** to an on-accent palette — text, borders, band
326
+ gradation, and even nested buttons / links / icon-tiles read on the accent
327
+ with no per-component code. It is theme-agnostic and derived (every value a
328
+ `color-mix` of the captured accent pair), and swaps the brand pair
329
+ (`primary` ↔ `on-primary`) without a CSS custom-property cycle by capturing on
330
+ the surface and inverting on descendants. A theme can override any role by
331
+ nesting a rule for its own `[data-theme=…]` under `[data-surface="accent"]`.
332
+ `FoldSurfaceName` gains `'accent'`. See `docs/surfaces.md`.
333
+ - **On-accent contrast contract.** A contract test derives the on-accent text
334
+ ramp per theme and asserts it clears a documented WCAG floor (the accent is an
335
+ emphasis surface — AA-large 3:1 — or the theme must override the ramp). No
336
+ eyeballing.
337
+ - **`fold-card` — an accessible interactive contract.** `interactive` cards are
338
+ now real controls: `role="button"`, `tabindex`, `Enter`/`Space` activation, an
339
+ `(activated)` output, an `ariaLabel` input, a visible focus ring, and
340
+ `prefers-reduced-motion` respected. The projected bands are neutral `<div>`s
341
+ (not `<header>`/`<footer>`), so a card is a single control with no nested
342
+ landmarks.
343
+ - **`fold-link` — `target` + `rel` for external links.** A linked `fold-link`
344
+ takes `target` (e.g. `_blank`) and `rel`; `rel` defaults to a safe
345
+ `noopener noreferrer` whenever `target="_blank"`, and `(clicked)` now emits the
346
+ `MouseEvent` (so cmd/middle-click and modifier state are observable).
347
+ - **`titan` theme — brushed titanium.** A fifth `[data-theme]`: a light, warm
348
+ brushed-steel read. A cool `steel` ground with the header + rails at the page's
349
+ own tint (a frameless top), bright polished cards floating off it on the shared
350
+ elevation shadow, and a heat-anodized `titanium` **copper-orange** brand.
351
+ Borders re-point to a **solid** steel primitive (a palpable machined seam, not
352
+ the alpha hairline the light themes share); corners soften a step in
353
+ `scales.css`. In the gallery both rails float as steel plates while the header
354
+ stays flat. Uniform-polarity, so no chrome override. The token contract (theme
355
+ parity, no-hex, no dead primitives) stays green.
356
+
357
+ ### Changed
358
+
359
+ - **BREAKING — `fold-card` band chrome is per-band.** `separators` and
360
+ `raisedBands` change from booleans to a `FoldCardBandChrome`
361
+ (`'none' | 'header' | 'footer' | 'both'`), so a header and a footer are dressed
362
+ independently. Migration: `<fold-card separators>` → `separators="both"`;
363
+ drop the attribute for `'none'`.
364
+
365
+ ### Fixed
366
+
367
+ - **`fold-avatar` — a broken image falls back to the initials** instead of the
368
+ browser's broken-image glyph, and retries when `imageUrl` changes. `ghost`
369
+ combined with `imageUrl` is no longer a silent no-op — a guest keeps the
370
+ dashed edge even with a photo.
371
+ - **`fold-avatar` — initials stay legible on every palette fill.** The initials
372
+ ink is now the higher-contrast of the dark/light pair (was a magic-threshold
373
+ guess), and a contrast contract asserts every built-in palette fill clears AA
374
+ (≥ 4.5:1). A status-ring perceivability contract locks the `ring` colours
375
+ against a WCAG 1.4.11 regression.
376
+
377
+ ### Docs
378
+
379
+ - **`docs/surfaces.md`** documents the auto-inversion principle and the
380
+ per-theme override seam; a `/surfaces` gallery page shows the live plain-vs-
381
+ accent proof. `docs/STRENGTHS.md` captures the top-tier design arguments.
382
+
11
383
  ## [0.3.0] - 2026-07-25
12
384
 
13
385
  ### Added
@@ -133,7 +505,10 @@ design-token stylesheet.
133
505
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
134
506
  strings localise via inputs / providers (`provideFoldPanelLabels`).
135
507
 
136
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.3.0...HEAD
508
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.5.1...HEAD
509
+ [0.5.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.5.1
510
+ [0.5.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.5.0
511
+ [0.4.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.4.0
137
512
  [0.3.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.3.0
138
513
  [0.2.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.1
139
514
  [0.2.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.2.0