@fasl-work/caos-app-shell 0.19.6 → 0.19.7

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 ADDED
@@ -0,0 +1,1237 @@
1
+ ## [0.19.007] - 2026-10-29
2
+
3
+ ### Fixed
4
+
5
+ - The version numbering reads as one release everywhere. npm showed only the semver form (`0.19.6`) while the tags, `VERSION`, the CHANGELOG and the README badge showed the display form (`0.19.006`), and nothing on the npm page said they were the same release: the package shipped neither `VERSION` nor the CHANGELOG. The package now carries both, the README shows the npm badge beside the release-tag badge and states the two spellings, and [`docs/09_versions.md`](docs/09_versions.md) maps every version published on npm to its display form, its tag and its commit, with the numbers that never reached npm (0.04.000; 0.06.009 to 0.06.012) and why. The npm uploads that had no tag (0.1.1, 0.1.2, 0.6.0 to 0.6.4) are tagged at the commit npm records for them; no published tag was moved or deleted. `docs/08_releasing.md` and the SDD state how a release reaches npm today: with the owner's direct-publish token from a clean clone of the tag, because `publish-npm.yml` has never published (every run answered E404 on upload: no Trusted Publisher is configured on the package page). Found by Felipe on 2026-10-29 ("why the numbering is not coherent"); SHR-001 to SHR-004. A product adopting it changes nothing but its pin.
6
+
7
+ ## [0.19.006] - 2026-10-28
8
+
9
+ ### Fixed
10
+
11
+ - The footer marks the product licence `translate="no"`: a licence identifier is a name, not a quantity, and an SPDX id that carries a version (Apache-2.0, GPL-3.0, CC-BY-4.0) was read as a number with a decimal point by gate G11 on every Spanish page, and could be mangled by browser translation. The licence text is unchanged, the deprecated `footer.license` path gets the same attribute, and no other footer text is marked (base defect BD-1, found by CEFOP_DinHot on 2026-10-28; SHF-037).
12
+
13
+ ## [0.19.005] - 2026-10-27
14
+
15
+ ### Added
16
+
17
+ - A product opts the notes of its filling cards out of the 35% cap with `data-caos-note-fit` on any ancestor, or `noteFit` on a `CaseWorkbench`, which marks its root: at 1280 px and wider, where the body keeps the stage floor, the note takes the height it needs and shrinks and scrolls in its own box only where the drawing needs its floor; below 1280 px the cap stays. CAOS_Contraste's C04 notes are data the reader needs whole, and the cap cut 9 to 117 px of them while the product could not restyle `.caos-plot-note` (the template's web-baseline guard refuses that) (known shell defect 46; SHC-101).
18
+
19
+ ## [0.19.004] - 2026-10-27
20
+
21
+ ### Fixed
22
+
23
+ - A lane badge in the workbench rail wraps inside the rail: the Spanish replay label of 0.19.3 ("Reproduce el resultado precalculado") is wider than the 248 px rail and, with `nowrap`, spilled out of it and widened it, so G5 reported "rail element(s) outside the rail" and the case select cut 14 px of content. A badge outside the rail keeps `nowrap` (known shell defect 44; SHW-044).
24
+ - Gate G6 judges a drawing only by the part of it inside the viewport, and only when that part is at least the drawing's minimum size (16 px for a canvas or a table, 48 px otherwise). A table card with 5 px of its body inside the fold of a 390 x 844 page was called blank ("table, 337x5 is blank") from a sliver of padding while it renders whole once scrolled to (CAOS_Contraste full gate; SHG-075). A blank drawing inside the viewport still fails.
25
+
26
+ ## [0.19.003] - 2026-10-27
27
+
28
+ ### Fixed
29
+
30
+ - The shell's own labels read in Spanish on a Spanish page: the lane badges say "Reproduce el resultado precalculado" and "Solo precalculado" instead of "offline", and a reference without a DOI is linked as "enlace" instead of "link" (CAOS_Contraste capture review U16-R3, batches c17 and c24; SHT-011).
31
+
32
+ ## [0.19.002] - 2026-10-25
33
+
34
+ ### Fixed
35
+
36
+ - Gate G5 now distinguishes controls inside a static content `<header>` from controls inside the sticky site header. The covered viewport area is computed from the actual sticky or fixed header, so document source links remain reachable in the tablet regression (SHG-074).
37
+
38
+ ## [0.19.001] - 2026-10-24
39
+
40
+ ### Fixed
41
+
42
+ - A filling `PlotCard` on a narrow screen (900 px and below) whose body holds flowing content takes the height of that content and scrolls sideways, instead of the 260 to 440 px clamp with hidden overflow that cut whole tables and their right columns at 390 px (CAOS_Contraste U16-R2b, eight review batches; SHC-100). Flowing content is a table that is the body's own child, or any element marked `data-caos-flow` (a stack of a drawing and its tables, a table in a scroll box, panels of small multiples); a body that holds a drawing alone keeps the clamp; nothing changes at 1280 px. Products stop restyling `.caos-plot-body` to get it (the template's web-baseline guard refuses that).
43
+ - An `.equation` that scrolls sideways on a narrow screen fades its right edge and keeps a gap after its end, so the cut reads as more to scroll to (moved into the shell from CAOS_Contraste, which restyled the shell's class; SHC-100).
44
+ - The time axis of a `UPlotChart` names months and weekdays in the page language (`es-CL`, `en-US`): a Spanish page read uPlot's English "Jan", "Apr" under its dates (CAOS_Contraste U16-R2b c15 M1; SHC-099). New: `dateNames` in the chart's interaction rules.
45
+
46
+ ## [0.19.000] - 2026-10-23
47
+
48
+ ### Added
49
+
50
+ - Tier A of the interactive-visualization rubric on every shell chart (the Contraste 1.00.000 capture review, U16-R2). On by default, no change at a call site (SHC-063 to SHC-085):
51
+ - `UPlotChart`: zoom by the wheel (while the chart holds the focus, Ctrl or Meta included, and only while that changes the range; otherwise the wheel, and the browser's page zoom, are the page's), by the brush (a drag under 5 px is a click) and by pinch; pan by Shift and drag and by touch; a visible **Reset zoom** control while zoomed, double-click, `0` and Escape; the keyboard model (arrows move the crosshair and its readout, Home and End, `+` and `-`, Shift and the arrows, Enter picks); one tab stop with an accessible name; a hidden data table and a one-line summary; the zoom survives a theme, a language and a size change. New props `title`, `zoomY` and `zoomOff` (the reason zoom is turned off, 12 letters and digits at least).
52
+ - `BarChart`: a crosshair band and a readout per bar (pointer and keyboard), zoom and pan by category (wheel, brush, Shift and drag, pinch, keys), the reset control, the same tab stop, table and summary; new prop `zoomOff`. Its markup is wrapped in `.caos-bars-host`, with the foot (readout and reset control) under the drawing.
53
+ - Gate G20: every chart is a tab stop with a name, a summary (`aria-describedby`) and a table (`aria-details`); driven once per state it moves its crosshair with the arrow keys, zooms with `+` and with a scripted brush, shows one reset control and resets with `0`; a chart that turns zoom off, an empty reason included, is listed among the waivers (a short reason fails). A hand-drawn SVG chart is not measured (`docs/07_gate.md`). Plants `chart-not-focusable`, `chart-no-name`, `chart-no-table`, `chart-no-reset`, `chart-reset-hidden`, `chart-no-zero`, `chart-no-arrows`, `chart-dead-brush`, `zoom-off-empty`, `zoom-off-unexplained` and `bar-repeat-ticks`.
54
+ - Text kit: `niceStepAt`; `repeatedLabels` moves to it (still exported from `chart`); the interaction rules live in `src/chart/interaction.ts`.
55
+ - `BarChart`, the two forms the products still drew by hand (D16-21; SHC-090 to SHC-093), each with the whole of Tier A and no change to a call site that does not use them:
56
+ - Floating bars: `BarDatum.span` gives the bar's two ends on the value axis (a waterfall of layers, a bridge of signed steps). `value` stays the number the bar states; the readout and the table add the ends ("from a to b", From and To columns); a span of zero length is a dot as thick as a bar; ends in any order draw the same bar; a span outside `axis.range` is cut to it; with a span `axis.range` is the axis as given and may exclude zero.
57
+ - Grouped bars: `BarDatum.group` puts consecutive data in one band (side by side when vertical, one under another when horizontal) with the group label printed once, `BarChartProps.legend` draws what each tone means, the readout reads "group, label: value", the table gains a Group column, and the keyboard, zoom, pan and brush go group by group.
58
+ - `UPlotChart`: the labels of x marks and y marks share one set of placed boxes (SHC-094, SHC-095): no x label lies over a y label or another x label (the Contraste U16-R2 forms V04-2 and V16-3), a label that fits no row is cut with an ellipsis and kept whole in the plot's title, the readout and a new table of marks. The host declares `data-mark-overlaps`, `data-mark-boxes` and `data-mark-cut`; `drawMarks` and `drawYMarks` take an optional `boxes` array and `drawMarks` returns the labels it cut.
59
+ - Gate G20 fails a chart whose `data-mark-overlaps` is above 0, and drives the floating and the grouped bars (the walk reaches every bar and reads its ends and its group; the table has From, To and Group; a declared legend is visible); five plants prove it (SHC-096).
60
+ - The re-review of 0.19.0: `BarDatum.format` (optional `FormatOptions`): the format of one bar's value, readout, ends and table cells, over `axis.format` (SHC-097).
61
+ - `placeYLabel` in the chart's interaction rules; `drawYMarks` returns the labels it cut or could not write.
62
+ - The gate's legit page `legit-bar-adoption` (the forms CAOS_Contraste adopted).
63
+
64
+ ### Fixed
65
+
66
+ - `BarChart`: a value axis of counts ticked at fractions and rounded them ("0, 1, 1, 2, 2"): it now ticks at whole numbers, and an axis of fixed decimals at multiples of their unit (SHC-078); it declares `data-ticks-repeat` and the gate fails a repeated label under G6, as for `UPlotChart` (SHC-079).
67
+ - The review of 0.19.0 (Opus, 2026-10-22), fixed before the release (SHC-065 to SHC-089):
68
+ - The brush works on every chart form: a parity plot, a chart with an overlay series and one with `x.range` ignored it, because uPlot's own `setScale` turned a fixed range back into the fixed one. The selection is applied through the zoom operations, ordered, inside the full range and not below the least span; the bars and the lines share one 5 px drag threshold.
69
+ - A vertical `BarChart` narrower than its value column draws no negative width and logs no error (it drew `rect` widths of -0.8 at 70 px); at 0 px it draws nothing.
70
+ - Zoomed to its least span, no axis repeats a tick label: an axis of significant digits (depth 1000 to 1490 m read "1,000" ten times) cannot be zoomed below the span its labels can tell apart (`leastLabelSpan`).
71
+ - On `x.time` the data table, the summary and the keyboard readout print dates in the page language, not epoch seconds.
72
+ - A wheel that cannot change the range (a wheel out at the full range, a wheel in at the least span) reaches the page, so a focused chart no longer stops the page from scrolling past it; a focused chart shows a 2 px ring. Ctrl or Meta with the wheel zooms a chart only while it holds the focus; over any other chart it stays the browser's page zoom.
73
+ - `zoomOff` of any text, an empty reason in one language included, sets `data-zoom-off`, and G20 fails an empty or short reason (it passed silently before).
74
+ - New data is drawn even when it differs from the data on show only by a swap of two values or by a change too small for a sum (the fingerprint is replaced by an element-wise `sameData`).
75
+ - The reset control is at least 24 by 24 px; the drawing of a `BarChart` is `role="presentation"` beside the named group (its name was said twice); the plot is described by its summary only and linked to its table by `aria-details`, so a screen reader no longer reads up to 1000 rows on every focus.
76
+ - The re-review of 0.19.0 (Opus, 2026-10-22) and the CAOS_Contraste adoption of the bar forms (U16-F8), fixed before the release (SHC-090 to SHC-098):
77
+ - A dot total at an end of `axis.range` ran past the plot: its value label was cut and the dot covered its category label. With a dot the value axis is inset by its radius at both ends (MJ1).
78
+ - A plain bar beside floating bars, on an axis that excludes zero, was drawn from zero across the category labels: it is cut to the axis (MJ2), and it reads its two ends as the table writes them, so G20 passes a chart that mixes the two (F8-2).
79
+ - A floating or grouped bar that takes a choice (`onSelect`) failed G5: the middle of its box was empty drawing. It is now a target over its whole slot (`rect[data-bar-hit]`) (F8-1).
80
+ - G20's scripted brush dragged a fixed share of the drawing, which on a short horizontal chart of three or four bars spanned every category, so it reported a working brush as dead: it drags between the bars (F8-3). The chart's brush was right.
81
+ - Reference-line labels close together overlapped (they were clamped at the left edge): a y label is placed against the shared boxes, on rows near its line, and cut with an ellipsis when no place clears (`placeYLabel`); G20 names each overlapping pair with its kind, x or y (m1). An x mark with an empty label no longer takes a row.
82
+ - The shell's test of V04-2 drew the same pixels before and after the mark labels of 0.19.0; it now draws Contraste's C04 chart and measures the ink (m2).
83
+ - Grouped members were told apart by colour alone: each is named on the drawing when its slot has room (m4). The summary of a floating chart compared a step with a total: it states the first and the last ends (m5). A highlighted bar with a tone looked like its neighbours: it is outlined (m3).
84
+ - `.prose svg { height: auto }` collapsed KaTeX's square-root SVG to under 2 px, so every root of a documentation page vanished: `.prose .katex svg` keeps its own height (SHC-080).
85
+ - An axis title never cuts: a rotated y title longer than the plot moves above it as a horizontal line, an x title wider than the chart wraps below it, and a `BarChart` title wraps over as many lines as it needs (SHC-081, SHC-082).
86
+
87
+ ### Changed
88
+
89
+ - The plot of a `UPlotChart` is now the first tab stop of the chart, before the series key (SHC-052 says so); the brush needs a 5 px drag, so a click that moves a pixel no longer zooms.
90
+ - `UPlotChart` and `BarChart` draw their Tier A markup only once the chart has a size; before that they render as in 0.18.0 (SHC-085).
91
+ - Styles: `styles.css` gains `.caos-bars-host`, `.caos-bars-foot`, `.caos-bars-readout`, `.caos-bars-brush`, `.caos-bars-cursor`, `.caos-chart-axis-title`, `.caos-chart-foot` and `.caos-chart-reset`; `reserved-classes.json` lists them.
92
+
93
+ ## [0.18.000] - 2026-10-20
94
+
95
+ ### Added
96
+
97
+ - `UPlotChart`: what CAOS_Fragmenta's own wrapper did and the shell's chart could not (CAOS_Fragmenta 0.09.000, BL-042). Each option is off by
98
+ default: a chart that uses none renders and declares as in 0.17.0.
99
+ - Named x ticks, `x.ticks: { value, label: BiText }[]`: the axis ticks exactly at those values and labels each in the page language; the
100
+ readout names an x on a tick by its label (within 1e-9 of the x span; a value listed twice keeps its first label); the host declares
101
+ `data-x-tick-labels` and drops it with the ticks; two ticks with one name, adjacent or not, fail G6, which names the label (SHC-047 to SHC-049, SHC-060, SHC-062).
102
+ - `x.range: [lo, hi]`: the x scale spans at least that range, as `y.range` fixes the y scale, so an end tick's label is not cut; on a log axis a range reaching 0 is ignored alone; on a parity chart it is ignored (SHC-050, SHC-061).
103
+ - `solo`: each entry of the key is a button (`aria-pressed`, Tab, click, Enter or Space) that dims every other series (alpha 0.12, 1 px) and a second
104
+ press releases; the chart is redrawn, not rebuilt; the host declares `data-solo` (SHC-051 to SHC-054). The solo is held by the series key, the new `ChartSeries.id` or else its English label, not by position (SHC-059).
105
+ - `ChartSeries.dots`: a dot at each value of a line, none at a null (SHC-055).
106
+ - A `yMarks` entry may omit `label`: the line is drawn with no label box and nothing reserved for one; a draggable one names its handle "Reference line", numbered when several are draggable (SHC-056, SHC-058).
107
+ - New exports `tickSplits`, `tickLabelAt`, `xScaleRange`, `namedRepeats`, `soloKey`, `soloStyle`, `SOLO_DIM_ALPHA`, `referenceLineName`, type `ChartTick`; the stylesheet gains `.caos-chart-key-button`.
108
+ - The gate fixture has a Categories view of each option and the plants `named-ticks-repeat` and `named-ticks-far`.
109
+
110
+ ## [0.17.000] - 2026-10-20
111
+
112
+ ### Added
113
+
114
+ - `UPlotChart`: draggable reference lines. A `yMarks` entry takes `drag: { min, max, step? }` and the chart `onYMarkChange(index, y)`; the line carries a slider handle the reader drags or moves with the arrows, Page keys, Home and End, inside its range, and the chart is redrawn, not rebuilt, while it moves (SHC-037 to SHC-041, BL-029). New exports `clampMark`, `nudgeMark`, type `YMarkDrag`; the host declares `data-y-drag`.
115
+ - `UPlotChart`: a `points` series with an `x` of its own is drawn over the plot on its own x values, the axes taking its extent in; the host declares `data-overlay` and `data-overlay-drawn` (SHC-042 to SHC-044, BL-029). New export `overlayExtent`.
116
+ - `parityBox` (the side of the square parity box, SHC-034 and SHC-035), `decadeOnly`; the gate fixture has a Forms view of each new form (a log x axis with a draggable percentile line, measured points on their own sizes and marks apart; a log axis with a draggable limit).
117
+ - Gate: G5 fails a select, in the App or in Focus, whose selected text does not fit the room it leaves, naming the route, the state, the text and
118
+ what was measured: for a case select with the shell's display, a name cut in its box (the real `.cs-select-text` element overflows, as a product
119
+ stylesheet that clamps it or an overflow the wrap cannot break, or the display's own box smaller than the name) and a stale display (the shown name differs from the option the select holds); for
120
+ any other select, a text wider than its content box less the native arrow (measured with a probe select, 0 with `appearance: none`). SHG-072,
121
+ plants `select-cut`, `select-cut-token`, `select-cut-display`, `select-display-stale`, `select-arrow`, `select-focus-rail`, and the pages that must pass,
122
+ `legit-select-two-lines` and `legit-select-three-lines`.
123
+ - Gate: under `--require-focus`, G18 fails a workbench route (`--workbench`) that is not among the measured routes, as 0.16.0 does for a
124
+ surface App route, so a run cannot measure no Focus at all (SHG-073, plant `workbench-route-unwalked`).
125
+
126
+ ### Changed
127
+
128
+ - `UPlotChart`: a log x axis labels decades whatever uPlot's tick filter does (`decadeOnly`, SHC-030 to SHC-033, CAOS_APP_SHELL#65): with uPlot 1.6.32 the labels are those of 0.16.0 (it already labelled only the ticks whose mantissa is 1) except a non-decade tick whose mantissa is 1, such as 0.15 on a 0.15 to 15 axis, which loses its label; the host declares the labels shown in `data-log-labels`. An axis whose range holds no power of ten keeps uPlot's labels.
129
+ - The `katex` peer range accepts 0.16 to 0.19 (`^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0`; it was `^0.16.0`, so a product pinning katex 0.19.0,
130
+ its current release, failed `npm ci` with ERESOLVE, CAOS_Fragmenta 0.09.000's dependency round). The shell develops and tests on katex 0.19.0,
131
+ which ships its own types (`@types/katex` dropped). 0.18.0 prefixed katex's internal classes; the classes the shell's stylesheet and the gate
132
+ select (`.katex`, `.katex-display`, `.katex-mathml`) are unchanged in 0.19.0, and the build and every suite pass on it.
133
+
134
+ ### Fixed
135
+
136
+ - The workbench's case select no longer cuts a case name (SHW-041, SHW-042, BL-074). Since 0.14.1 the **Focus** control shared the select's
137
+ row and, at 1280x800, a name was cut inside the native select with no ellipsis (CAOS_Fragmenta 0.08.001: "Mina de cob"). The control now ends
138
+ the group tab row, in the slot `Tabs` reserves (`trailing`), as a surface's does since 0.16.0, whatever the case picker (select, chips or
139
+ none); the select takes the rail head's whole width. Measured at 390, 768 and 1280 px: the rail head, the tab row and the panel start are the
140
+ same with and without `focus`. Focus keeps its URL, Escape, exit button and return to the same case, group and sub-view; inside Focus there
141
+ is no control, as before. `CaseSelectorProps.accessory` stays for a product's own control and still shares the select's row.
142
+ - `CaseWorkbench` no longer drops a product's own `cases.accessory` while `focus` is on: in 0.16.0 and before, a select picker with `focus` replaced the
143
+ accessory by the Focus control, so the accessory never showed in the App. It now shows on the select's row, and the Focus control is in the tab row.
144
+ - The case select shows real case names whole (SHW-043; the consumer trial of 0.17.0 at a584b08: CAOS_Fragmenta failed 112 states and CAOS_Contraste 413,
145
+ all `select.cs-select[data-control="case"]`, with names of 202 to 311 px in 181 px of room, such as "Degenerate design: the negative control" at
146
+ 241 px and "Corporate PD across 1 to 5 year horizons (planned)" at 311 px; the template passed with short names). `CaseSelector` with
147
+ `layout: 'select'` now shows the selected name whole, at any length, in a display (`.cs-select-display`, `aria-hidden`) with the native
148
+ `select` kept in place on top of it, transparent by colour: the keyboard, the screen reader (accessible name and value) and the native list are
149
+ unchanged. There is no line cap and no ellipsis: the name wraps to the lines it needs and a token wider than the room breaks inside
150
+ (`overflow-wrap: anywhere`); the display is as tall as the longest name of the visible list needs at the current width, measured in the DOM
151
+ (the display's own font, padding and content width) and re-measured when the list, the width or the fonts change, so the rail head never moves
152
+ when the reader switches cases, and a list whose names all fit keeps one line as tall as a plain select. Same in the App rail and the Focus
153
+ rail. The consumer trial of 8e6e254 (CAOS_Fragmenta 8 states, CAOS_Contraste 90, names of 354 and 398 px that need three lines in 182 px) is why
154
+ there is no cap: any cap cuts the next longer name; a product that finds its rail head too tall shortens its names. The display names the case the
155
+ native select holds (the selected case, else the first enabled visible case, as React selects it: a selected id outside the visible source lane no
156
+ longer shows another case than the select). Under forced colours (Windows High Contrast) the native select stays transparent, so its one cut line
157
+ is not painted over the display; the focus ring is the display's alone, on `:focus-visible`.
158
+
159
+ ## [0.16.000] - 2026-10-20
160
+
161
+ ### Added
162
+
163
+ - Focus mode for a `SurfacePage` App (SHF-031 to SHF-036, BL-068, ADR-0070). `SurfacePage` takes `focus?: boolean | SurfaceFocusConfig`
164
+ (`param`, `onChange`) and each view with a stage takes `TabDef.focus?: boolean | ViewFocusConfig` (`title`, `description`, `hud`,
165
+ `rail`). While the open view is focusable, a **Focus** button ("Focus" / "Enfocar", named "Open this view in focus mode" / "Abrir
166
+ esta vista en modo enfoque", new `FOCUS_TEXT.enterViewLabel`) sits at the end of the tab row, in a slot beside the tab list (`Tabs`
167
+ takes it as `trailing`), never in the head and never inside the `tablist`, with its space always reserved while the surface has `focus`
168
+ and any view sets it: on a view with no stage it is hidden from sight, the accessibility tree and the keyboard (`visibility: hidden`,
169
+ `aria-hidden`, `tabindex=-1`), so the tab row and the head do not move and no row is added. Measured on a headless surface, a
170
+ title-only one, one with title and actions and one with a lede, at 390, 768 and 1280 px: the tab row's top and height and the head's
171
+ height are the same on every view and equal to a surface without `focus`; at 390 px the tabs scroll beside the control. It opens the view in `FocusShell`
172
+ (`landmark={false}`: one `main`), the view as the stage and its `rail` as the parameter column, the title the view's label unless
173
+ given, the HUD per view. The URL holds `?focus=1` beside `?view=`; Escape or the exit button return to the same view and sub-view
174
+ (the view memory `CaseWorkbench` keeps), drop the parameter and return keyboard focus to the Focus button; the page behind is
175
+ `inert`. A link with `focus=1` for a view with no stage, an unknown view, or no view when the first has no stage opens no Focus and
176
+ the parameter is removed. A view with no `rail` leaves the stage the whole frame (`FocusShell` `rail` may now be omitted, class
177
+ `caos-focus-norail`) and keeps its own controls in its body. The frame carries `data-surface-focus`, `data-focus-view` and
178
+ `data-focus-group`; the surface and the frame also carry `data-view-param`, the query parameter that holds the open view (`view`, or the
179
+ product's `deepLinkView` name), which the gate reads. Why: a console App (CAOS_Neuraxis: Tasks, Investigations, Engine) is a `SurfacePage`, and a surface with a
180
+ simulation could not meet ADR-0070 on the shell. Without `focus` a surface renders exactly as in 0.15.0.
181
+ - `useFocusMode(config, { enabled, initialAllowed })`, exported with `FocusMode`, `FocusModeConfig` and `FocusModeOptions`: the one
182
+ Focus behaviour (the URL parameter, Escape, the inert page, keyboard focus in and out, the view memory) that `CaseWorkbench` and
183
+ `SurfacePage` share; for a product with a frame of its own.
184
+ - Gate: G18 on an App that is a surface (SHG-058 to SHG-062). With `--workbench ''` the gate measures the route of the new option
185
+ `--app-route` (default `/app`) as a surface: it opens every view of its tab row, enters Focus from each view that offers the control
186
+ (from its last sub-view; outside the primary mode, from the last such view), and applies the failures it applies to a workbench with
187
+ the view and sub-view in place of the case and the group: another view or sub-view in Focus or after the return, a URL in Focus that
188
+ does not hold `?focus=1` and the view, a `?focus` left in the URL after the return, a page or a rail that scrolls, a tab row that
189
+ wraps, a stage under half the viewport, a stage control under the Focus chrome, a Focus control inside the frame, an exit control that
190
+ does not work, Tab leaving the frame, a `main` inside a `main`, and a Focus control that does not open Focus. With
191
+ `--require-focus` a surface none of whose views offers the control fails with "the surface App (/console) offers no Focus control
192
+ ([data-focus-enter]) on any of its 3 view(s)". The fixture gains the route `/console` (a surface with a view with no stage, a view with a
193
+ rail, a HUD and sub-views, and a view with no rail), a clean run of it at 390 and 1280 px in both languages and themes, and sixteen
194
+ plants `surface-focus-*`. The gate also fails (SHG-063 to SHG-066) a surface whose tab row or head moves between views, an Escape that
195
+ does not return (each focusable view is left both by the exit button and by Escape in the primary mode), a Focus URL that drops the
196
+ view parameter whatever its name, and, under `--require-focus` with `--workbench ''`, an App route that is not among the measured
197
+ routes. The fixture gains a headless surface that holds its view in `?mode=` (the shape of CAOS_Neuraxis), which passes.
198
+ - `--spa-fallback` for `caos-shell-gate --serve` (SHG-067): the server answers as nginx does for a single-page app on a VPS
199
+ (`try_files $uri $uri/ /index.html`): an extensionless path that matches no file gets `index.html` with 200; a path with an extension
200
+ that matches no file stays 404, so a missing artifact still fails. Default off: the server still answers as GitHub Pages does. Edge
201
+ paths are documented in `docs/07_gate.md` and tested: a directory with no index answers the app shell, a dot in a directory name
202
+ reads as an extension (404), a missing dotfile answers the app shell (an existing file, a dotfile included, is served as it is), the query string is no part of the path.
203
+ - The Focus frame (workbench and surface) carries `data-focus-param`, the query parameter that holds Focus; G18 reads it instead of the
204
+ literal `focus` (a product that renames it with `param` was failed falsely, and a renamed parameter left after the return passed).
205
+ G18 also fails a surface with Focus that renders a head with no title, lede or actions (an empty head row), and its message for a
206
+ tab row or head that moves between views no longer blames the Focus control. It also fails a tab row taller than its tabs need
207
+ (a taller Focus control in the slot adds height on every view; plant `surface-tall-control`).
208
+
209
+ ### Changed
210
+
211
+ - `CaseWorkbench` keeps its Focus behaviour in `useFocusMode`, no longer in its own state and effects. Its DOM, its URL behaviour and
212
+ its tests and plants are unchanged. `FocusShell`'s `rail` prop is optional (a rail is still rendered when one is given).
213
+ `window.__caosGate.focusFacts()` reads a surface App's instrument as well as a workbench's, preferring the workbench, then the Focus
214
+ frame, then the surface, by explicit order.
215
+ - `FocusShell`: `rail={null}` (or no `rail`) now renders no rail column and the frame is one column (class `caos-focus-norail`); before
216
+ it rendered an empty rail column. A direct consumer of `FocusShell` that passed `rail={null}` gets a frame whose stage is the whole
217
+ width; pass an empty element to keep the column. `CaseWorkbench` always passes a rail and is unchanged.
218
+ - `SurfacePage` with `actions={null}` renders no actions element and, with no title or lede, no head (before, a null `actions` rendered an
219
+ empty head row; an `undefined` one was already ignored).
220
+
221
+ ### Fixed
222
+
223
+ - A language switch inside Focus no longer loses the reader's sub-view on the return (SHW-040). The view memory keyed a tab row on its
224
+ localized `ariaLabel`, so the key changed with the language; it now keys on the row's kind, its place and its ids. The place is the
225
+ path of the open panels above the row (`PanelPathContext`, provided by `Tabs` and `SubTabs` around their open panel and by the Focus
226
+ frames), so two sub-view rows with the same ids in different groups or views keep separate selections, and a row honours its own
227
+ `initial` (keying on the ids alone would have merged them). This is a defect of Focus on a workbench since 0.14.0, not of the
228
+ surface; it is fixed for both.
229
+ - The DOI of a reference is printed inside `translate="no"` (SHD-086), in the end-of-page references and in the reference list, so G11
230
+ (Spanish numbers) no longer reads the dotted number of an identifier such as `doi:10.1523/ENEURO.0159-20.2020` as a decimal point on a
231
+ Spanish page. A plain number with a decimal point in a Spanish paragraph still fails G11.
232
+ - The bibliographic text of a reference (the citation: title, venue, edition or version, publisher) is printed inside `translate="no"` in
233
+ the end-of-page references block and the reference list (SHD-087): it is the source's own and is never translated, so G11 no longer
234
+ reads `Version 3.4` in a NIST title as a Spanish decimal (about 300 false failures on CAOS_Aerovia's documentation pages). The block
235
+ heading and the `link` label stay translatable; the DOI handling above is unchanged. A plain `3.4` in a Spanish paragraph of the same
236
+ page still fails G11 (plant `decimal-point-plain-34`). The citation text also leaves G19 (values whole), which skips `translate="no"`
237
+ as it does G11; a value in a bibliographic title is not measured either.
238
+ - A `CaseWorkbench`, or a `SurfacePage`, placed inside a `Tabs` panel keeps the reader's sub-view entering and leaving Focus: the Focus
239
+ frames provide the inherited panel path plus the open id (`PanelPathContext`), not an absolute one (found in the re-review before
240
+ the release; no current consumer nests them).
241
+
242
+ ### Migration
243
+
244
+ - A product whose App is a `SurfacePage` and that has a simulation or a dynamic scenario (ADR-0070): set `focus` on the `SurfacePage`
245
+ and `focus` on each view with a stage (a simulation, a drawing, a live run; not a list or a form); move that view's controls into its
246
+ `focus.rail` (they stay in its body when it has no rail; the view's content renders in both places, so the product shows the control
247
+ in the body only outside Focus, as the fixture's engine view does); the control sits on the tab row, so the head needs no title or
248
+ actions; then run the gate with `--workbench '' --require-focus` (and `--app-route` when the App route is not `/app`). A
249
+ surface App with no simulation changes nothing; a product whose App is a `CaseWorkbench` changes nothing.
250
+
251
+ ## [0.15.000] - 2026-10-15
252
+
253
+ ### Added
254
+
255
+ - Gate check G19, values whole (SHG-057, BL-066). In every measured state, a short value written in the page's text must sit on
256
+ one line. A value is a run of characters without breaking whitespace in one text node (U+00A0 and U+202F do not end it), 2 to 16
257
+ characters long once the sentence's own punctuation is stripped from its ends, with a digit, at most 4 letters and no other
258
+ character than digits, signs, separators, currency signs and `% ‰ × °`: `35%`, `0,96`, `US$1.2bn`, `2026-10-15`, `-3,2pp`,
259
+ `ADR-0071`. The probe `window.__caosGate.brokenValues()` cuts each value into segments right after a hyphen, a dash or a slash,
260
+ reads each segment's `Range` and counts the groups of its client rects by top; a segment on more than one line fails the value as `"35%" breaks across 3 lines (<element>)`, also when it is longer than its container,
261
+ and runs in every route, size, theme and language, in the wide-font pass and in Focus mode. Why: shell 0.14.2 fixed a share that
262
+ broke into "3", "5", "%" at 390px on every product's executive view, and none of G5 to G10 saw it, because the pieces are neither
263
+ clipped nor outside their box. Not read: a value split across elements (`<b>35</b>%`), SVG text (G10), canvas text, `code`, `pre`,
264
+ `kbd`, `samp`, KaTeX, `translate="no"` and `aria-hidden` content; `12 %` with a regular space is two runs and may wrap. The
265
+ self-test plant `value-broken` reproduces the pre-0.14.2 row (a shrinking flex value with `overflow-wrap: anywhere`), the plant
266
+ `value-broken-range` cuts `599-653` inside `653`, and the clean fixture passes in every state.
267
+ - Adopting 0.15.0: a product may meet new G19 failures it did not have under 0.14.x. Each names the value and the element: a number,
268
+ percentage, amount or unit that a narrow container, a flex row that lets the value shrink, or `overflow-wrap: anywhere` on a
269
+ parent cuts into pieces. Give the value `white-space: nowrap` (and `flex: none` in a flex row), or write it with U+00A0 between
270
+ the number and its unit where the unit may wrap away. A break right after a hyphen, a dash or a slash is a normal
271
+ line break and passes (the fixture's citation pages `599-653` break at the hyphen at 390px in Spanish and pass); only a break inside
272
+ a segment between those places fails. No shell component or stylesheet changed in this release.
273
+
274
+ ### Fixed
275
+
276
+ - `e2e/animated-illustration.test.ts` waits (at most 3 s) for the paused final frame of the SMIL illustration before it measures
277
+ the bar: the shell pauses SMIL at its last frame in an effect and the frame is sampled later, and a slow CI runner read the bar
278
+ at its first frame (80px of 640px). Test only; the component is unchanged.
279
+
280
+ ## [0.14.003] - 2026-10-15
281
+
282
+ ### Fixed
283
+
284
+ - Focus mode: the stage label no longer covers the stage's controls. On Contraste's C10 at 1280px the title ("Portfolio credit
285
+ capital: ASRF, Vasicek fit, copula MC") wrapped to two lines and the label sat over the open group's sub-tab row, because the
286
+ stage cleared the label by a fixed 4.6rem (6.6rem under 900px). `FocusShell` now measures its overlay chrome (the label, the HUD
287
+ tiles, the actions; a layout effect, one `ResizeObserver`, the `resize` event, values written only when they change) and sets
288
+ three lengths on `.caos-focus-stage`: `--caos-focus-clear-top`, `--caos-focus-clear-bottom` and `--caos-focus-label-bottom`.
289
+ The `CaseWorkbench` focus stage pads by them (4.6rem and 6.6rem remain the fallbacks before the first measurement and in a
290
+ non-browser render), the desktop HUD starts under the measured label instead of at a fixed 104px, and under 900px the stage clears
291
+ a bottom HUD. A product that renders its own instrument in `FocusShell` can use the same properties. No prop changes (SHW-038).
292
+ - Focus mode: the Focus control no longer shows inside Focus. Since 0.14.1 the rail inside the focus frame carried the Focus button
293
+ beside the case select, a control that opens the mode the reader is already in. The focus frame's rail head is now the case
294
+ picker and the variant bar without it, in every layout; a product's own `cases.accessory` still shows, and the App's rail head is
295
+ unchanged (SHW-037).
296
+ - Gate G18 now fails both: a stage control under the Focus chrome (the stage label, the HUD or the Focus actions) and a Focus
297
+ control inside the Focus frame (SHG-056). The self-test plants `focus-chrome-covers` and `focus-enter-inside` prove it, and the
298
+ clean fixture now has a Focus title that wraps at 1280px, so every clean run walks the wrapping path.
299
+ - The repository's CI base-integrity guard judged a tracked `.env` wrongly both ways: a real `.env` passed whenever a root
300
+ `.env.example` was tracked, and a tree whose only example file is nested failed. The step now reads `git ls-files` once and
301
+ tests it on here-strings (`git ls-files | grep -q` also fails at random under pipefail, as the products' CI found); any `.env`
302
+ other than an `.example` file fails. `test/ciGuards.test.ts` runs the step itself on planted trees (red on the old step).
303
+
304
+ ## [0.14.002] - 2026-10-15
305
+
306
+ ### Fixed
307
+
308
+ - The executive view's "Where it comes from" shares stay whole at phone width: the cause row lets its label wrap anywhere, and its
309
+ squeezed value broke into "3", "5", "%" at 390px (seen on the template's example and on every product's executive view). The value
310
+ no longer shrinks or wraps (`.caos-exec-cause-head > .caos-exec-value`); `e2e/executive-view.test.ts` counts the line boxes of every
311
+ visible value at 390px in both languages (it fails without the fix).
312
+
313
+ ## [0.14.001] - 2026-10-15
314
+
315
+ ### Fixed
316
+
317
+ - The Focus control of `CaseWorkbench` sits on the case select's row (`CaseSelector` takes an `accessory`, rendered after the select
318
+ in one row, the select shrinking to make room), so the rail head is no taller with it. On a row of its own it added 37px to the
319
+ rail head, and rails with little slack scrolled 6 to 10px (ADR-0071 rule 6: Contraste's C04 and C22, 19 gate failures when it
320
+ adopted 0.14.0). With case chips, or with no case picker, the control keeps a row of its own, as in 0.14.0. The visible word
321
+ ("Focus" / "Enfocar") and the accessible name are unchanged (SHW-031).
322
+
323
+ ## [0.14.000] - 2026-10-14
324
+
325
+ Flip cards, the card carousel, animated illustrations and Focus mode for the App (CAOS_MANAGE `plans/app-shell/0-14-0-plan.md`,
326
+ backlog BL-060 to BL-063). Additive: a product that changes nothing renders as before, except that the executive view's four
327
+ card groups now flip. The gate gains G16, G17 and G18.
328
+
329
+ ### Added
330
+
331
+ - `FlipCard` (exported): a small card whose face shows its title and whose back shows its content after a click, Enter or Space,
332
+ and back again. A real button carries `aria-expanded` and `aria-controls` on each face; keyboard focus moves to the face that
333
+ shows; the hidden face is out of the accessibility tree and the tab order (`aria-hidden` and `inert`) while its content stays in
334
+ the markup; Escape closes; `prefers-reduced-motion: reduce` swaps the faces without the turn; theme tokens only; the face says it
335
+ turns (an icon and a hidden "Show details" / "Mostrar detalles"). The rule of use: SMALL cards only (a title of at most 8 words
336
+ in English and 10 in Spanish, a back of at most 40 and 50), where the reader scans the titles and opens the ones that matter.
337
+ - `CardDeck` (exported): a list of cards plain, as flip cards (`flip`), as a row up to `carouselAbove` cards (default 4) and as a
338
+ carousel above it: as many cards as fit (at least one), previous and next buttons, swipe on touch, arrow keys, Home and End, a
339
+ position ("2 of 5" / "2 de 5"), no autoplay, every card reachable by keyboard, never a sideways scroll of the page or its panel;
340
+ reduced motion removes the slide. With `FLIP_LIMITS`, `FLIP_TEXT`, `DECK_TEXT`, `perPageFor`, `deckWindow` and `deckPosition`.
341
+ - The executive view shows `pain.bearers`, `situation.roles`, `situation.state_of_the_art` and `proposal.four_cs` as flip cards
342
+ (a row of four or fewer, a carousel of five); `context.forces`, `trends.items` and `proposal.actions` stay plain. The data
343
+ contract (`executive.schema.json` 2.0.0) is unchanged.
344
+ - Animated illustrations: an executive illustration (or any figure) may be an SVG with CSS keyframes or SMIL inside the file, a
345
+ gentle loop or a single play of at most ten seconds, theme tokens, labels that stay put, and a `@media (prefers-reduced-motion:
346
+ reduce)` block that shows the static final frame. The shell pauses a SMIL animation at its last frame under reduced motion, and
347
+ `cleanSvg` removes an animation that writes a link or an event handler. `analyseSvgAnimation` and `animationFailures` (in
348
+ `gate/areas.mjs`) are the analysis the template's guard script reuses.
349
+ - Focus mode for the App: `CaseWorkbench` takes `focus` (`true` or `CaseFocusConfig`). A Focus button in the rail head opens the
350
+ selected case's open group in the full viewport through `FocusShell` (the stage is the view, the rail stays), holds `?focus=1`
351
+ beside `?case=` and `?view=`, and returns with Escape or the exit button to the same case, group and sub-view (the open sub-tab of
352
+ each tab row is kept across the frame change). `removeQueryParam`, `FOCUS_TEXT` and `FOCUS_PARAM` are exported; `FocusShell`
353
+ marks `data-focus-shell`, `data-rail` and `data-focus-exit`. The plan named `WorkbenchShell`; the case, group and view live in
354
+ `CaseWorkbench`, so the control is there, and `WorkbenchShell` is unchanged. `WorkbenchLayout` exports its rail body as `RailBody`.
355
+ - The gate: G16 (flip cards and card decks: the size limits, real buttons, Enter and Space, focus, the hidden face out of the
356
+ accessibility tree, the carousel reaching every card without a sideways scroll), G17 (animated drawings: the reduced-motion block,
357
+ ten seconds at most, still under emulated reduced motion) and G18 (Focus mode, entered by clicking for every case and left by the
358
+ exit control and by Escape: the same case and group, no page scroll, the rail without scroll, one tab row); `--require-focus`
359
+ fails an App without a Focus control. 13 plants prove them. The executive-view checks read the back of a flip card and the cards
360
+ a carousel hides, so the floors, the 4 C's and the writing limits still apply to them.
361
+
362
+ ### Fixed (review of 0.14.0)
363
+
364
+ - The executive-view checks of the gate are back at their 0.13.1 strength: a flip card's back is read with the card turned, hidden 4 C's and hidden bearers' details fail ADR-0017.2 again; only a card the carousel's window hides is exempt.
365
+ - Focus mode makes the page behind it inert and no longer nests a `main` in the App's `main` (`FocusShell` takes `landmark={false}`).
366
+ - G16 checks that an opened back shows content, that a hidden face keeps no reachable control and that a carousel without controls hides no card; G17 analyses `img` SVG figures and multiplies finite iteration counts; G18 starts from the last group and sub-view, compares both, keeps Tab in the frame and refuses a nested `main`; 10 plants.
367
+ - The carousel's focus guard moves focus to the toggle of the face that shows.
368
+
369
+ ### Changed
370
+
371
+ - The fixture product's executive view holds five bearers (a carousel) and an animated `pain` illustration.
372
+
373
+ ## [0.13.001] - 2026-10-13
374
+
375
+ ### Fixed
376
+
377
+ - `STANDARD_ROUTES` is again App, Introduction, Methodology, Implementation, Experiments, Benchmark: the App is the leftmost
378
+ tab, always (Felipe, 2026-10-13). 0.13.0 had put the Introduction first when the landing moved to it; the landing is
379
+ unchanged (the root and unknown paths open the Introduction's executive view through `Landing`) and no longer moves
380
+ the tabs. A product on 0.13.0 needs no change beyond the version: its header reads the order from `STANDARD_ROUTES`.
381
+ - Every documentation route takes the full width of the page, the Introduction included (Felipe, 2026-10-13: the
382
+ Introduction did not use the full width while Methodology, Implementation, Experiments and Benchmark did): `DocPage`
383
+ always renders the wide document layout; its `wide` prop is kept for compatibility and changes nothing.
384
+ - The order test (`test/workbench.test.tsx`), the navigation end-to-end test (`e2e/landing.test.ts`), requirement SHF-011
385
+ and the docs (`docs/01_structure.md`, `docs/06_executive-view.md`) state the order.
386
+
387
+ ## [0.13.000] - 2026-10-13
388
+
389
+ ### Changed
390
+
391
+ Every product lands on its executive view, and the view is the standard Felipe set on 2026-10-13 (CAOS_MANAGE `conventions/problem-context-first.md`): an executive frame of the industry's problem, not of the product, never tied to a client and never a pitch. A landing block (the problem's title, a key statement, two or three headline figures with linked sources) sits above five sub-tabs (the pain, the context, the current situation, the trends, the proposal with the 4 C's Choice, Context, Cost and Control), each opening with its key message line and holding its depth floor. Sources are links. The view and every lede take the full width of the document column. BREAKING: `executive.schema.json` 2.0.0 replaces the 0.12.0 fields; the App moves to `/app` (old links forward with their query string), one router edit per product.
392
+
393
+ - The executive view is redefined (schema 2.0.0, breaking): it frames the industry's problem for a decision maker, general,
394
+ never tied to a client and never a pitch (CAOS_MANAGE `conventions/problem-context-first.md`, decided 2026-10-13). The data file
395
+ is `title`, `key_statement`, `headline_figures` (2 or 3: heading, value, unit, source), one object per sub-tab (`pain`,
396
+ `context`, `situation`, `trends`, `proposal`, each with its `key_message` and its supporting fields at the floors of the
397
+ structure as minimum and maximum counts of rows), the three `illustrations` `pain`, `situation` and `proposal`, and `sources`.
398
+ The 0.12.0 fields (`executive`, `organisation`, `initiative`, `why_now`, `vision`, `outcomes`, a top-level `four_cs`,
399
+ `dialogue`) are gone; a product rewrites its `content/executive.json` and its three illustrations (`docs/06_executive-view.md`
400
+ has the fields, the floors and the writing limits).
401
+ - The view shows a landing block (the title, the key statement, the headline figures) above five sub-tabs titled "The pain",
402
+ "The context", "The current situation", "The trends", "The proposal" (Spanish "El problema", "El contexto", "La situación
403
+ actual", "Las tendencias", "La propuesta"), each opening with its key message line, then the numbered linked sources and two
404
+ onward links ("How the work explores this problem", "Open the App"). The sections come from one definition,
405
+ `EXEC_SECTION_DEFS` in `executive.ts`. No rubric label and no rubric question is printed; the gate fails a view that prints one.
406
+ - The 4 C's are required in the proposal: `FOUR_CS` holds Choice, Context, Cost and Control with Felipe's definitions in English
407
+ and Spanish (2026-10-13), `proposal.four_cs` has exactly those four rows in that order, each with `applies` (what the C means
408
+ for the proposal). The pending state of section 3 is gone.
409
+ - Sources are links: each `sources` entry is `{ id, title, url, note? }` with an https `url`; every figure and every claim of the
410
+ state of the art and of the trends cites one by id, every source is cited, and the notes number in the order of first use. The
411
+ validator and the gate fail a source without a url, with a non-https url, over the writing limits, or citing a file name or
412
+ number in place of the link (`file 7`, `archivo 7`, a `.pdf` name, "Published on").
413
+ - Extra rules of the validator, mirrored by the gate on the rendered page: the causes' weights add to at most 100; each action's
414
+ owner is a role of the current situation; a paragraph has at least 25 words; one sentence for the key messages, captions,
415
+ actions and checks, two for the key statement.
416
+ - General, never a client, never a pitch: the validator and the gate fail promotional or client-bound wording ("our client",
417
+ "nuestro cliente", "contact us", "contáctenos", "buy", "comprar", "price", "precio", "offer", "oferta" and their plurals, whole
418
+ words, either case) in any text field; one gate plant per word.
419
+ - Gate: it gathers the facts of every sub-tab it visits (both languages) and checks the landing block, the five sub-tabs, the key
420
+ message lines, every floor on the rendered rows, the 4 C's, the sources, the onward links, the rubric text, the full width and the
421
+ illustrations; about fifty executive plants. The `G5: shrunk-tabs` plant is repaired (below).
422
+ - Full width: the lede of every `DocPage` takes the full width of the document column (the 70ch measure on `.lede` is gone) and the
423
+ executive view takes the full width of the column too (an illustration may stay centred inside its figure). A browser test
424
+ measures both at 1280 and 1600 px; the gate fails a lede or an executive block narrower than the column (plants `lede-narrow`,
425
+ `exec-narrow`). In the shell's own browser harness only the lede was narrow; a narrower Introduction in a product comes from the
426
+ product's own CSS or a non-prose wrapper.
427
+ - Landing (breaking for the router of a product): every product lands on the Introduction with its executive view open. New
428
+ exports `Landing`, `LANDING_ROUTE` and `APP_ROUTE`; `STANDARD_ROUTES` is now Introduction (`/introduction`), App (`/app`),
429
+ Methodology, Implementation, Experiments, Benchmark. A product changes its router once: `/` and `*` render `<Landing />`, `/app`
430
+ renders the App, `fixedRoutes` names `/app`, a static site materialises `app/index.html` (`docs/06_executive-view.md`, The
431
+ landing). An old App link such as `/?case=B2#x` is forwarded to `/app?case=B2#x`. The gate default `--workbench` is `/app`,
432
+ checks that the root opens the Introduction on its executive view and that an old App link keeps its query string and hash
433
+ (plant `landing-app`); its static server keeps the query string through its trailing-slash redirect.
434
+ - The gate plant `G5: shrunk-tabs` failed on develop and on the 0.12.0 release commit in the current Chromium (153.0.8010.12): the
435
+ plant shrinks the tab row with a flex rule, which that Chromium absorbs without overflowing the tabs, so the gate's cut measure
436
+ read zero. The plant now also caps the row's height below its tabs, which reproduces the cut that known shell defect 16 describes;
437
+ the gate's measure is unchanged.
438
+ - The executive view's source notes wrap a long word explicitly (`overflow-wrap: anywhere`, as its facts and why-now
439
+ line already do; BL-053). A long URL in a note was reported to widen a 390 px page by CAOS_Contraste BL-070; the
440
+ overflow was not reproduced in the shell's browser test or in Contraste's build, and the rule makes the wrap explicit
441
+ either way.
442
+ - The executive-view browser test renders the view as products do (a `DocPage` with the two `Tabs` of an
443
+ Introduction) and adds a long-URL note case at 390 px.
444
+
445
+ ## [0.12.000] - 2026-10-12
446
+
447
+ ### Changed
448
+
449
+ The executive view of every CAOS app is one standard rendered by the shell (CAOS_MANAGE `conventions/problem-context-first.md`): the four sections of Felipe's executive-pitch rubric, verbatim, from one validated data file, with three authored illustrations of the initiative, the problem and the proposed solution.
450
+
451
+ - `ExecutiveView` and `executive.schema.json` 1.0.0: the executive view of every Introduction is now one component fed by the
452
+ product's `content/executive.json` (four sections with Felipe's rubric titles and questions, three inline illustrations,
453
+ numbered source notes). It takes no children; invalid data renders an error state listing each field path. Exports:
454
+ `ExecutiveView`, `validateExecutive`, `FOUR_CS`, `EXECUTIVE_SCHEMA_VERSION`. `FOUR_CS` is empty until Felipe names the 4 C's;
455
+ section 3 shows its declared pending state meanwhile.
456
+ - BREAKING for products: the Introduction areas `initiative`, `outcomes`, `positioning` and `credibility` are removed from
457
+ `DOC_AREAS` and from the gate's table; a section declaring one fails as an unknown area. The executive view cannot be waived.
458
+ - Gate: a new executive-view check on `/introduction` (the view is the shell's `ExecutiveView`, first and default, its data
459
+ valid in the page, the writing limits on the rendered text in both languages, nothing placed beside it, readable fields,
460
+ one source note per figure, and for each illustration: place, caption, inline, loaded, theme tokens only, no `data-chart`,
461
+ labels inside the viewBox and at least 9 px). Self-test plants added (`exec-*`); the plants of the removed areas are replaced.
462
+ - Styles: `.caos-exec*` classes for the view; `reserved-classes.json` regenerated.
463
+ - Docs: `docs/06_executive-view.md`; requirement rows SHD-034 to SHD-047.
464
+ - Package: `executive.schema.json` is published (`files`) and exported as `@fasl-work/caos-app-shell/executive.schema.json`.
465
+
466
+ ## [0.11.002] - 2026-10-12
467
+
468
+ ### Fixed
469
+
470
+ Found by CAOS_Contraste 0.07.000 on the release gate of shell 0.11.1 (G6 at 138 px, Spanish, 1280x800, wide font):
471
+
472
+ - The filling card's stage floor now holds on the stage itself: at 1280 px and wider the body of a `PlotCard fill` (the
473
+ box the gate measures) keeps at least `--caos-stage-floor` (140 px), so the card grows when its header or its note
474
+ wraps and the containing panel scrolls. 0.11.1 derived a card height from a fixed chrome, which a header wrapping to
475
+ a third line defeated (a 400 px column with a note left 106 px). The browser test now covers cards without a note and
476
+ the narrow column of a three-view row.
477
+
478
+ ## [0.11.001] - 2026-10-12
479
+
480
+ ### Fixed
481
+
482
+ Found by CAOS_Contraste 0.07.000, whose CI web baseline (correctly) refused the product rule it had needed:
483
+
484
+ - A filling `PlotCard` keeps a minimum height of its own at 1280 px and wider, derived from its layout (the header, the padding and the note's 35% cap), so its drawing keeps the gate's stage floor (140 px) when filling cards share a short column, in every language and in the wide font. Products no longer restyle `.caos-plot.fill` to pass G6. Below 1280 px nothing changes. The gate fixture's Fit tab stacks two filling cards in one column.
485
+
486
+ Adoption: a product drops any rule of its own that gave `.caos-plot.fill` a minimum height (CAOS_Contraste's
487
+ `@media (min-width: 1280px) { .caos-plot.fill { min-height: 23rem; } }`).
488
+
489
+ ## [0.11.000] - 2026-10-12
490
+
491
+ ### Added
492
+
493
+ The shell's first software design document (ADR-0075) and the gate that holds it to its tests (#100):
494
+
495
+ - `docs/design/SDD.md`: the problem and non-goals, the dimensioning of `src/` measured by a script (45 modules,
496
+ 4,251 lines), technology and versions, the public contracts (entries, stylesheets, reserved classes, the `data-*`
497
+ attributes the gate reads, the gate CLI), the components by area, how each component behaves without data, with
498
+ partial data, in error and while loading, verification, versions and adoption, risks, ADR fit.
499
+ - `docs/design/features/<area>/requirements.md`: nine areas, 188 EARS requirements at release, each naming the test or gate
500
+ plant that verifies it; every test file and every plant is cited.
501
+ - `scripts/check_sdd.py` (`npm run check:sdd`, a step of the `guards` job): the ten sections in order, unique
502
+ `SH[DFWCKNTAG]-NNN` ids, SHALL in every statement, and every gate reference resolved to a test defined in the named
503
+ file (comments stripped) or to a plant id in `test/gate-selftest.mjs`; its own tests run in CI too; after an adversarial review it rejects names inside strings, comments or another test's body, skipped and todo tests, `describe` blocks, plain paths, paths outside the repository, malformed rows and stray ids, planned or superseded areas without a reviewed `.status-allow` entry, and SDD headings inside fences or comments.
504
+ - CONTRIBUTING.md and docs/08_releasing.md: a change starts with its requirement row, then a failing test that names
505
+ it, then the code.
506
+
507
+ The required content of the documentation pages, declared by the page and measured by the gate (ADR-0017 section 2.1;
508
+ the core of every product is its tailoring to a real problem, so the Introduction opens for a decision maker):
509
+
510
+ - The Introduction has two views: `executive` first (areas `initiative`, `outcomes`, `positioning`, `credibility`,
511
+ in business language, no equation, every value claim a number with its unit and source) and `technical` (`problem`,
512
+ `principles`, `math` with a symbol glossary, `pipeline`, `honesty`). The executive view cannot be waived.
513
+ - Methodology, Implementation, Experiments and Benchmark each have their named areas and floors (the tables in
514
+ `docs/04_views.md`, from one table, `gate/areas.mjs`).
515
+ - A section declares what it covers: `<DocSection area="...">` (and `kind="classical" | "learned"` for a method).
516
+ New components: `Stat` (a value, its unit, its source), `Question` (a business question and its answer) and
517
+ `Glossary`. `DocPage` takes `floorWaivers`: a floor a page cannot meet is declared with its reason, listed in the
518
+ gate report, and reviewed with the pull request that adds it.
519
+ - The gate fails (`ADR-0017.2`) a page that lacks a required area, or whose area lacks its required content, in either
520
+ language, counting only what a reader can see where the glyphs are painted. The executive view must be the view the
521
+ page opens on; no Introduction content sits outside the two views; a tab list inside a section belongs to that
522
+ section and is judged across every state; a Methodology method cites references; the Benchmark's live panel needs a
523
+ registered control. The language of a page stays a reviewer's duty.
524
+
525
+ Adoption: a product on 0.11.0 fails the gate until its five documentation pages declare their areas and its
526
+ Introduction carries the executive view. Write the executive brief first, in the product's design document
527
+ (CAOS_MANAGE `conventions/problem-context-first.md`; the template's `docs/guides/06_executive-brief.md` and
528
+ `07_documentation-pages.md`), then transcribe it to the page.
529
+
530
+ Contract-first products declare missing data instead of drawing an empty chart (ADR-0080; #99):
531
+
532
+ - The gate accepts an instrument that draws nothing and shows a `data-state="unavailable"` note a reader can see and
533
+ read (its glyphs painted inside the instrument at a legible size, not clipped, not transparent, at least 12 letters or digits in two words). An invisible, clipped or
534
+ empty note, a reason of punctuation, or any drawing element rendered beside the note fails G6, each with its own
535
+ message. The gate checks that a reason exists, not that it is true.
536
+
537
+ The gate self-test grows from 37 to 98 plants, one per check and per trap, with legitimate pages that must pass (a method with its own view tabs, outcomes as a list). Two adversarial reviews tried to break these checks; every case they found is a plant.
538
+
539
+ `yMarks` for reference levels (#94):
540
+
541
+ - A tone per line (`warn`, `bad`, `good`, `neutral`), and a y range that takes every line in, so a level outside the
542
+ data stays in view.
543
+
544
+ ### Changed
545
+
546
+ Documentation pages (#96, found on CAOS_Contraste's Methodology):
547
+
548
+ - Text blocks of `.prose` take the full width of the document column; captions keep their measure and stay centred.
549
+ - References are one block at the end of the scope that shows them: the open tab or sub-tab panel, otherwise the end
550
+ of the `DocPage` (`section.caos-doc-references`, each citation once, in the order of first appearance). `DocSection`
551
+ registers its `refs` with the nearest scope instead of rendering a `Refs` row; `ReferenceList` is superseded.
552
+ - The gate's ADR-0017.4 fails a bibliography outside the end-of-page block, a references block that is not last, and
553
+ sections that cite references the page never lists (plants `refs-mid`, `refs-missing`).
554
+
555
+ A filling card (#95):
556
+
557
+ - A `PlotCard fill` note keeps at most 35% of the card and scrolls beyond it.
558
+ - The gate fails a drawing under `--stage-floor` (140 px by default) at 1280 px and above (G6, plant `stage-floor`).
559
+
560
+ ### Fixed
561
+
562
+ - Known shell defect 35: a knob's range input kept the user agent's side margins, so at phone widths it ran past a
563
+ clipping card (#91). Products drop their `margin-inline: 0` override.
564
+ - The end-of-page references tests drive Chromium and now run with the browser suite (`e2e/`); develop CI had failed
565
+ since #97 because the unit job installs no browser.
566
+
567
+ ## [0.10.000] - 2026-10-08
568
+
569
+ ### Added
570
+
571
+ What CAOS_Fragmenta's parity plot still drew on a canvas of its own (CAOS_APP_SHELL#84; CAOS_MANAGE `plans/app-shell`
572
+ BL-029): with these a product draws predicted against measured, its null model and the case the reader picked on
573
+ `UPlotChart`.
574
+
575
+ - `yMarks`: horizontal reference lines, each labelled at its right end above the line, inside the plot, haloed
576
+ (`drawYMarks`; the host declares `data-y-marks`).
577
+ - `ChartSeries.size`: the marker diameter of a `points` series (9 px by default), so the selected case can sit in a
578
+ larger series of its own colour.
579
+ - On a chart whose series are all `points`, the cursor, the readout, `onCursor` and `onPick` take the point nearest the
580
+ pointer in the plane (`nearestPoint`); uPlot alone takes the nearest in x, which on a scatter named a point far above
581
+ or below the pointer. The readout names only the series that have the point.
582
+ - `readout(index)`: the caller's text for the point under the cursor, in place of the list of values (a scatter
583
+ names its case: the blast, its site, its error); `hint`: the readout at rest.
584
+ - A parity plot's range takes in its reference lines, so a null model's level is always in view; its two axes tick
585
+ at one step (uPlot spaces x and y ticks differently, so one range read 25, 30, 35 across and 24, 26, 28 up); filling
586
+ a box taller than it is wide, it is centred down as well as across.
587
+ - Every chart declares how many ticks each axis labels (`data-ticks-x`, `data-ticks-y`).
588
+ - `/chart` exports `drawYMarks` and `nearestPoint`.
589
+ - The gate fixture's parity plot carries a reference line and a selected point; `e2e/parity.test.ts` hovers and
590
+ clicks beside a point whose neighbour in x lies far below it, and reads the reference line off the canvas (it fails
591
+ with the nearest-in-x cursor).
592
+
593
+ ### Fixed
594
+
595
+ - The lock file still said 0.9.3 after 0.9.4; it follows `package.json` again.
596
+
597
+ ## [0.09.004] - 2026-10-08
598
+
599
+ ### Fixed
600
+
601
+ Found by CAOS_Contraste's case C04 and the 0.9.3 gate on its build:
602
+
603
+ - Known shell defect 32: a value on the scientific boundary was written in scientific notation when it was a percent.
604
+ `formatNumber` writes a magnitude below 1e-4 as `1E-5`, and a percent is the value times 100; 1e-6 times 100 is
605
+ 9.999999999999999e-5 in binary floating point, so a log axis of PDs in percent labelled its 1e-6 tick `1E-4 %`
606
+ between `0.01 %` and `1 %` (CAOS_Contraste C04, Fitch's generators; #81), and `formatTicks` turned a whole axis
607
+ scientific for the same tick. The boundary is now compared with a relative tolerance of 1e-12: `0.0001 %`; a value
608
+ truly below it (9.99e-5) stays `9.99E-5`. `test/base09.test.tsx` holds both, the percent, the plain value and the
609
+ ticks, in English and Spanish.
610
+ - Known shell defect 33 (#85): the hover rule `.chip:hover:not(:disabled)` outranked `.chip.on`, so the active chip
611
+ under the pointer, the one a reader has just clicked, wrote its label in the text colour on the accent: 3.04:1 in
612
+ the light theme and 1.64:1 in the dark, under G13's 4.5:1 (every rail section the gate clicks on Contraste). The
613
+ case selector's `.cs-chip` lost its accent the same way. The hover rules now leave the active chip alone;
614
+ `e2e/chips.test.ts` hovers the active chip, an inactive one and the chosen case's chip in Chromium in both themes.
615
+
616
+ On adoption: nothing to change; a tick or a cell at exactly 0.0001 % reads in fixed notation, and an active chip keeps
617
+ its colours under the pointer.
618
+
619
+ ## [0.09.003] - 2026-10-08
620
+
621
+ ### Fixed
622
+
623
+ - A `Knob` in the rail drew its value under its label: the rail's rule for a labelled control
624
+ (`.caos-wb-rail label`, a column) outranked `.caos-knob-head` (a row, the value at the right), so every knob in
625
+ every rail took one more line than it is designed to, since 0.7.0 (CAOS_APP_SHELL#77). Found by the 0.9.1 gate on
626
+ CAOS_Fragmenta, its one failure left of 890 states: at 1280x800 in Spanish with the wide font, a rail of eight design
627
+ variants and three knobs held 710 px in 686 (ADR-0071 rule 6). The rail's rule now leaves the knob head alone; a
628
+ labelled select in the rail still stacks its text above it. `e2e/rail.test.ts` measures both in Chromium.
629
+
630
+ On adoption: a product's rail gains about 25 px per knob; nothing to remove.
631
+
632
+ ## [0.09.002] - 2026-10-07
633
+
634
+ ### Fixed
635
+
636
+ Found by CAOS_Contraste's case C04 on 0.8.x to 0.9.1:
637
+
638
+ - Known shell defect 31 (#71): a `UPlotChart` log axis whose values reached below about 1e-22 was never drawn. uPlot
639
+ ticks a log axis at 1 to 9 times every power of ten from a precision table that ends there, and below it threw
640
+ "Invalid array length" after stalling the page for several seconds (C04's time-homogeneity p-values of 1e-124 on
641
+ thousands of ratings). Below `LOG_TABLE_FLOOR` (1e-22) a log axis, x or y, now ticks at powers of ten, at most eight
642
+ (`logDecades`), and keeps every label; above it uPlot's own ticks are unchanged. `e2e/charts.test.ts` draws p-values
643
+ down to 1e-124 and 1e-300 on a log y axis and on a log x axis in Chromium with no page error (without the fix every
644
+ case below the floor fails), and reads a year without a group separator in the cursor readout (defect 30). On
645
+ adoption: a product that floored p-values only to keep a chart alive may draw them as they are; a floor for reading
646
+ stays the product's choice.
647
+
648
+ ## [0.09.001] - 2026-10-07
649
+
650
+ ### Fixed
651
+
652
+ Found by the 0.9.0 gate on CAOS_Fragmenta (2,860 failures in 889 states, most of them the gate's own):
653
+
654
+ - G10 judged a drawing against every ancestor that hides its overflow, the contained `.app-shell` included, which is as
655
+ high as the viewport: every documentation figure below the fold read "lies outside the drawing" (2,470 of the
656
+ failures). A drawing is now judged against the containers between it and its scroll container; what lies beyond the
657
+ scroll container is a matter of scroll position, not a cut.
658
+ - G13 read SVG text against the page behind the drawing, so a label in the page colour on a bar measured 1.06:1. It now
659
+ takes the background from the last filled shape painted under the text's centre, composited over the page.
660
+ - The light `good` (`#197c36`) and `magenta` (`#b93785`) cleared 4.5:1 on every surface but not on the `accent-soft`
661
+ highlight (4.46 and 4.44): a selected row of a table carries its tone text there (Fragmenta's comparison table). The
662
+ test of the palette now counts the highlight as a ground.
663
+ - G13's message named an SVG text's CSS colour instead of the fill it is painted with, so a light label on a red bar
664
+ was reported as dark text; the measure was right, the message misled.
665
+
666
+ The gate fixture carries each case in its clean run: a figure after five documentation sections, values in the page
667
+ colour on the checkpoint bars, a highlighted row with good-tone text. The 0.9.0 gate fails that fixture (G10 2, G13 8);
668
+ 0.9.1 passes it.
669
+
670
+ ## [0.09.000] - 2026-10-07
671
+
672
+ ### Added
673
+
674
+ What CAOS_Fragmenta still carried after 0.8.1 (CAOS_APP_SHELL#65; CAOS_MANAGE `plans/app-shell` BL-027, BL-028): with
675
+ these a product draws its size distributions, its parity plots and a weighted views row on the shell.
676
+
677
+ - `ViewsRow`: views side by side, each in a column the shell owns (a filling card keeps its height chain), the row
678
+ split by `shares` (`[3, 2]`); stacked below 900 px. A product removes its own column wrapper (Fragmenta's
679
+ `.fr-viewcol`).
680
+ - `UPlotChart`: `x.log`, a log x axis that spans the data (uPlot rounds out to the next decade) and leaves the ticks it
681
+ does not label blank; `parity`, predicted against observed on one shared range in a square box with the identity
682
+ line, x unsorted allowed; `onPick(index)`, the point under the cursor on a click. Every index handed back is the
683
+ caller's, also when the chart sorted an unsorted x (`sortOrder`).
684
+ - From the seventh series a repeated colour is dashed, in the plot and in the key (`seriesStyle`).
685
+ - `/chart` exports `seriesStyle`, `sortOrder` and `parityRange`.
686
+ - The gate fixture's Validation has a "Fit" sub-tab: a `ViewsRow` of three fifths and two fifths holding a parity plot
687
+ over an unsorted x (picking on) and a log-x curve; the clean run (G15) measures both.
688
+
689
+ ### Fixed
690
+
691
+ - G11 let a number in scientific notation through: "2.7e-08" on a Spanish page writes its decimal with a point, and the
692
+ exponent hid it from the pattern (found on CAOS_Fragmenta's Implementation page). The exponent is now part of the
693
+ number; the `decimal-point` plant carries one.
694
+ - The gate's header-route step (every route reached by a pointer click) let a locator wait its default 30 s and
695
+ throw, which ended the whole run (the 0.9.0 self-test, under load); a link that does not answer in time is now a G5
696
+ failure of its route.
697
+
698
+ ## [0.08.002] - 2026-10-07
699
+
700
+ ### Fixed
701
+
702
+ - Known shell defect 30 (#62): `formatNumber` grouped every number, so a calendar year on an axis, in a readout or in a
703
+ table read 2,021 (2.021 in Spanish), for every option the type allowed; `time: true` was no way out, since the
704
+ chart's cursor readout still formatted the epoch seconds. `FormatOptions.grouping: false` drops the group separator
705
+ on every path (fixed decimals, significant digits, the integer path), and `formatTicks` and `UPlotChart` pass the
706
+ axis format through, so `x: { format: { decimals: 0, grouping: false } }` writes 2021 on the ticks and in the
707
+ readout, in both languages. The default still groups. Found by CAOS_Contraste's case C04 (rating cohorts 2000 to
708
+ 2025). On adoption: a product that counted its years from a base year, or wrote them as text to avoid the
709
+ separator, gives its year axis `grouping: false`.
710
+
711
+ ## [0.08.001] - 2026-10-07
712
+
713
+ ### Fixed
714
+
715
+ Found by the 0.8.0 gate on the product template's example, the first product on 0.8.0:
716
+
717
+ - `BarChart` filling its card capped its rows at 2.2 times their height, so three horizontal bars took a quarter of a
718
+ tall card and failed the gate's stage-fill floor (G6, 13 to 29 percent against 30). The rows now share the card's
719
+ height; a bar keeps a readable thickness (half its row, at most 40px) with its label centred on it.
720
+ - G10 judged a label against the viewport and a header stuck at its top, so on a phone a chart's first row, scrolled
721
+ under the sticky header by the probe, read as "cut by 4px at the drawing's edge". A label is now judged against its
722
+ drawing and the containers that cut their content (overflow hidden or clip) only; a scroll container and the
723
+ viewport do not cut.
724
+ - G6 reported a table wider than a phone, scrolling inside its card as ADR-0071 rule 3 asks, as "extends past the
725
+ page". A surface inside a box that scrolls sideways ends where that box ends; one cut by a box that hides its
726
+ overflow still fails.
727
+
728
+ The gate fixture's comparison is a horizontal `BarChart` filling its card, and its "Every day" table is wider than a
729
+ phone; the clean run (G15) passes both only with these fixes.
730
+
731
+ ## [0.08.000] - 2026-10-07
732
+
733
+ The base defines the structure every product builds on (#59; CAOS_MANAGE `plans/app-shell`, research in
734
+ `wip/template-archetype/base-unify-audit-2026-10-07.md`). A survey of the 89 React frontends of the line found 44 on the
735
+ shell, 43 of them restyling its classes, 15 to 19 writing the same containment for a tab App, 16 to 24 holding their
736
+ tab rows by hand, and 29 carrying their own uPlot wrapper; this release moves those into the shell. Documentation:
737
+ `docs/` (structure, tokens, navigation, views, drawings, icons and numbers, the gate, releasing).
738
+
739
+ ### Added
740
+
741
+ - `SurfacePage`, the third route type beside the workbench and the document: a head (title, lede, actions), one row of
742
+ at most six views, the open view filling the viewport under `contain` and scrolling inside it.
743
+ - The open view in the URL: `CaseWorkbench` holds its open group and `SurfacePage` its open view in `?view=`
744
+ (`deepLinkView`); `useUrlView` for a tab row of a product's own.
745
+ - Tokens for type (`--text-*`), space (`--space-*`), radii (`--radius-*`), layout (`--measure`, `--measure-text`,
746
+ `--header-h`, `--rail-w`, `--fade`), icons (`--icon-*`) and z-order (`--z-*`), all in `SHELL_TOKENS`; one breakpoint
747
+ set, 480, 760 and 900 px (and the gate's 1280), exported as `BREAKPOINTS`.
748
+ - `BarChart`, a categorical chart without uPlot: margins measured from the labels, labels broken at a word and then
749
+ shortened with their title, round ticks in one notation, values in the interface language, `highlight`, `tone`,
750
+ `onSelect`.
751
+ - The text kit for a product's own drawings: `textWidth`, `widestLabel`, `fitLabel`, `niceTicks`, `niceStep`,
752
+ `fontFamily` (from CAOS_Fragmenta 0.07.000).
753
+ - `formatTicks` (one notation per axis), `NBSP`, `SCIENTIFIC_BELOW`, the `notation` option of `formatNumber`, the
754
+ type `ShellColorToken`; `/chart` exports `drawMarks`, `tickSteps` and `repeatedLabels`.
755
+ - A skip link to `main#main`, the first keyboard stop of every page.
756
+ - Icons sized by their context: lucide's `svg.lucide` in the header, tabs, sub-tabs, chips, buttons, route links,
757
+ badges, callout titles, plot heads and the console sidebar; a product passes no size.
758
+ - Gate: G10 text in drawings (labels cut, outside the drawing or overlapping, in every declared chart, instrument SVG
759
+ and document figure), with a wide-font pass (Verdana, else DejaVu Sans) at 390 and 1280 px; G11 a decimal point on a
760
+ Spanish page; G12 a vertical sub-tab list that scrolls away; G13 text under WCAG AA contrast; G14 `index.html`
761
+ beside the report, every capture by route with the failures first; options `--wide-font-sizes` and
762
+ `--no-wide-font`. The self-test (G15) plants a defect for each: 34 plants.
763
+
764
+ ### Changed
765
+
766
+ - `styles.css` is written in ordered layers (tokens, base, frame, routes, navigation, content, controls, case
767
+ selector, workbench, frames), each class defined in one place (`.tablist` and `.subtablist` were defined twice, the
768
+ second overriding the first); class names are unchanged.
769
+ - Every tab row (`.tablist`, `.subtablist`, the rail's section row) holds its height in any flex column, not only in
770
+ the workbench: a product removes its own `.tablist { flex: 0 0 auto }` and `.tab { flex: 0 0 auto }`.
771
+ - Text blocks inside a document (paragraphs, list items, callouts, captions) keep a 78ch line (`--measure-text`);
772
+ figures, tables and grids keep the width. A product removes its own `max-width: 78ch` on callouts and captions.
773
+ - The faint text colour, and in the light theme `accent-2` and `warn`, were raised to WCAG AA on every surface: the
774
+ faint text read 3.49:1 on the dark raised surface and 4.08:1 on the light one (dark `--color-fg-faint` `#828b97`,
775
+ light `#656d77`; light `--color-accent-2` `#0a7886`, `--color-warn` `#946300`). A unit test computes every pair.
776
+ - `reserved-classes.json` lists `components` (never restyled) and `modifiers` (`on`, `active`, `fill`, `wide`, ...,
777
+ which a product may join to its own class); `classes` keeps both for 0.7 readers. The words read from the
778
+ `@import` lines (`css`) are gone. The gauge legend dot is `.caos-gauge-dot`.
779
+ - The rail's section row fades its hidden end, reveals the open section and takes the arrow keys, Home and End.
780
+
781
+ ### Fixed
782
+
783
+ - Known shell defect 26: `html, body { overflow-x: hidden }` computed body's vertical overflow to auto, so body was a
784
+ scroll container that never scrolls and nothing in a scrolling document could stick to the viewport: the site header
785
+ scrolled away on every default-mode page and on a phone. `overflow-x: clip` (with `hidden` as the fallback). Below
786
+ 900 px a contained document's page is released in both forms of its rule (the `:has()` form had kept it a scroll
787
+ container).
788
+ - Known shell defect 24 (#55): the vertical sub-tab list is sticky; it stays in view at the end of a long section.
789
+ - Known shell defect 23 (#54): a filling `PlotCard` in a `.caos-views-row` takes an equal share; a product removes its
790
+ wrapper column (CAOS_Fragmenta `.fr-viewcol`).
791
+ - Known shell defect 22: a percent is joined to its sign, and a unit to its value (`Knob`, `Readout`, `Gauge`, the
792
+ chart readout), by a no-break space.
793
+ - Known shell defect 19: an axis writes all its ticks in one notation.
794
+ - Known shell defect 20 (the gate): a control whose inputs are all disabled is not moved by the reactivity check.
795
+ - `UPlotChart` marks: drawn with an explicit left alignment (uPlot left the canvas right-aligned after its y axis, so
796
+ the template's "peak" label sat on the wrong side of its line and ran off the plot near the left edge), on the side
797
+ of the line where they fit inside the plot, on separate rows when they would overlap, with a halo; a mark outside
798
+ the x range is not drawn.
799
+ - `UPlotChart` y axis: measured in the font uPlot draws with at the device pixel ratio; it was too narrow on every
800
+ high-density screen (the gate runs at ratio 1).
801
+ - The gate's truncation check normalised the letter `s` instead of whitespace (`/s+/g`); its pointer probe takes a 1x1
802
+ hidden control (the skip link) for what it is, and scrolls a control clear of a header stuck at the top of the
803
+ viewport; the tab walk does the same before it clicks.
804
+ - `CaseSelector` keeps the router's history state when it writes `?case=` (it replaced it with `null`).
805
+
806
+ ### Adoption (ADR-0078 section 5)
807
+
808
+ Pin `0.8.0` exactly. Remove: containment CSS for a tab App (use `SurfacePage`), tab-row flex and wrap overrides, chip
809
+ styling, `max-width: 78ch` on callouts and captions, product wrappers for a views row, label-fitting code that the
810
+ text kit replaces, hand-drawn bar charts (use `BarChart`), icon `size` props. Run the gate: G10 to G13 may report what
811
+ the product carried unseen.
812
+
813
+ ## [0.07.003] - 2026-10-06
814
+
815
+ ### Fixed
816
+
817
+ - Known shell defect 25: the gate's pointer probe (G5) scrolled an element that fits its scroll container until its
818
+ end showed plus an 8px margin, so an element with less slack than the margin had its start pushed out of the
819
+ container and was reported as never brought into view, by the probe that had moved it (CAOS_Contraste's
820
+ Methodology "Stability" sub-panel: 706.7px in a 712.2px page box at 1280x800, four false failures). The probe now
821
+ scrolls by the smaller of the end's overflow plus the margin and the start's distance from the container's start,
822
+ on both axes (#56). The gate fixture's Methodology gains a "Margins" tab, a focusable region a few pixels shorter
823
+ than the page box below the fold, which the clean run (G15) passes only with the fix.
824
+
825
+ ## [0.07.002] - 2026-10-05
826
+
827
+ ### Fixed
828
+
829
+ - Known shell defect 21: an axis of integer values (variant numbers, grades, years) formatted with `decimals: 0` got
830
+ uPlot's default tick steps, which include 0.5, 0.25 and 2.5, and the formatter rounded them: CAOS_Contraste's AUC
831
+ chart of seven variants read 1, 2, 2, 3, 3, ..., 7, 7, so every point shared its label with the tick beside it, and
832
+ a grade axis of three points read 1, 1, 1, 2, 2, 2, 2, 3, 3, 3. An axis whose format fixes its decimals now ticks
833
+ only at multiples of the smallest difference its labels can show (scaled by 100 for a percent); a time or log axis
834
+ keeps uPlot's own steps. `test/base07.test.tsx`.
835
+ - The gate could not see a repeated tick (the labels are drawn on a canvas). The chart host now declares
836
+ `data-ticks-repeat`, the ticks whose label repeats the one before, and the gate fails a chart that declares any
837
+ (G6), so an axis formatted by significant digits that still repeats is caught; the self-test plants one
838
+ (`repeat-ticks`, G15).
839
+
840
+ ## [0.07.001] - 2026-10-05
841
+
842
+ ### Fixed
843
+
844
+ Five base defects, four of them found by the first measured gate run of a product built on 0.7.0 (CAOS_Contraste,
845
+ 583 failures in 655 states); each has a test that fails without its fix.
846
+
847
+ - Known shell defect 14: the footer wrote a separator before the build group, whose auto margin moves it to the
848
+ right edge, so a "·" dangled at the end of the left group on every wide screen (found on Fragmenta). The
849
+ separator is gone; the margin separates the two groups. `test/appShellFixed.test.tsx`.
850
+ - Known shell defect 15: `formatNumber` wrote any magnitude in fixed notation, so a p-value of 1e-200 rendered as
851
+ two hundred zeros and one table cell measured 12,790 px. A non-zero magnitude below 1e-4 is now written in
852
+ scientific notation with the requested significant digits (`6.53E-13`, `7,05E-5` in Spanish); a fixed number of
853
+ `decimals` stays the caller's explicit choice. `test/base07.test.tsx` (S8).
854
+ - Known shell defect 16: in the workbench the group row and the sub-tab row are scroll containers, so their automatic
855
+ minimum height is zero, and under a panel taller than the instrument they shrank: the rows were clipped and the
856
+ panel covered the sub-tabs. Both rows keep their height (`flex: none`) and the panel scrolls, and the gate fails a
857
+ tab row that cuts its tabs (G5). `test/base07.test.tsx`; the gate fixture's "Every day" sub-tab and the
858
+ `shrunk-tabs` plant (G15).
859
+ - Known shell defect 17: a chart of several series named them only in the cursor readout, so at rest a reader could
860
+ not tell one line from another. `UPlotChart` now keys every series under the plot when there are two or more: its
861
+ colour, a dashed swatch for a dashed series, a dot for points, and its label; the key wraps.
862
+ `test/base07.test.tsx`.
863
+ - Known shell defect 18 (the gate): the pointer probe judged whether an element fits against the window instead of
864
+ the scroll container it lives in, and aligned a tall element by its end, so a documentation tab panel taller than
865
+ the page's scroll box was reported as "cannot be brought into the viewport" or "covered by" the header. The probe
866
+ now brings a large element in by its start and points at the centre of the part that shows, inside every ancestor
867
+ that clips. The gate fixture's "Notes" tab (taller than the scroll box, shorter than the window) passes clean.
868
+
869
+ ## [0.07.000] - 2026-10-04
870
+
871
+ ### Added
872
+
873
+ - The workbench primitives, so a product composes its App route instead of re-deriving it (ADR-0016 s9,
874
+ ADR-0017 and ADR-0071 as amended 2026-10-04):
875
+ - `WorkbenchLayout`: a sized control rail beside the instrument on the full viewport (`.page-body.wide`),
876
+ `min-width: 0` down the tree, the instrument marked `data-instrument`; a rail given as sections shows one
877
+ section at a time instead of scrolling (ADR-0071 rule 6).
878
+ - `CaseWorkbench`: the variant bar, then ONE row of at most six question groups in a fixed order (the
879
+ instrument groups, the variant comparison, the context write-up); more than six throws. The live lane is a
880
+ property of each view, shown by its badge.
881
+ - `VariantBar` (one scrollable row of regimes, the active regime's note and lane) and `LaneBadge` (live,
882
+ replay, offline only).
883
+ - `Readout`, `Gauge`, `Verdict` and `PlotCard`, with shared tones.
884
+ - `DocPage` and `DocSection`: the documentation route skeleton; a section ends in its own `Refs`, and a
885
+ section that cites nothing must state why (`noRefsReason`).
886
+ - `pick` and `BiText` for bilingual strings.
887
+ - `ShellConfig.contain`: every route is the viewport; the workbench fills it and a documentation route scrolls
888
+ inside the main container, so the document never scrolls (ADR-0071 rule 1).
889
+ - The design-system CSS for chips (`.chip`, `.chip.on`), the one-row chip strip, the workbench grid, the
890
+ variant bar, lane badges, readouts, gauges, verdicts and plot cards. Products no longer copy it.
891
+ - `caos-shell-gate` (bin): measures a running build at three sizes, both themes and both languages (viewport
892
+ fit, one-row tab bars, a rail without scroll, the instrument at least half of a workbench route, centred prose,
893
+ no bibliography dump, captioned equations, sections with references, the theme applied, no console error) and
894
+ reaches every header route by a real pointer click. `playwright` is an optional peer dependency.
895
+
896
+ - ADR rules enforced at runtime (ADR-0078, "rules live in the base"): an `Equation` without a caption, a citation
897
+ without a DOI or URL, a duplicate citation id, a `Cite` of an unknown id, a `ReferenceList` (banned by
898
+ ADR-0017 s4, now deprecated) and an architecture modal with fewer than five tabs are each reported with
899
+ `console.error`; `caos-shell-gate` fails a page on any console error.
900
+ - `STANDARD_ROUTES`: the six product routes with EN/ES labels, one definition for header, router and gate.
901
+ - `THEME_BOOT_SCRIPT`: the inline pre-paint script that applies the stored theme and language before the first
902
+ frame; `AppShell` keeps `<html lang>` in step with the language toggle (ADR-0011).
903
+ - The base requirements of the 2026-10-04 failure history (S1 to S13, S17, S18), each with a test in
904
+ `test/base07.test.tsx`:
905
+ - S3 `PanelBoundary`: a failing view is contained, named (`data-panel-error`) and reported; every tab panel,
906
+ every `PlotCard` and the shell root sit inside one.
907
+ - S4 `Tabs` and `SubTabs` are controllable (`value`, `onChange`), render only the open panel inside a boundary,
908
+ carry `data-tab` and `data-panel`, report more than six peers, and fade their overflowing edge;
909
+ `TabGroups` puts views under question groups when one row cannot hold them.
910
+ - S7 to S10 the workbench state: `Provenance` (`real`, `synthetic`, `published`) beside the lane on every
911
+ `Readout`, `PlotCard` and workbench group; `makeStateKey` and `useWorkbenchState`, so a view showing data
912
+ for an earlier selection is overlaid as stale; `CaseWorkbench` writes `data-case`, `data-variant`,
913
+ `data-source`, `data-state-key` and `data-replay-only` for the gate.
914
+ - S8 `formatNumber` and `useFormat`: numbers follow the interface language (es-CL decimal comma), and an
915
+ absent or non-finite value reads "not available" instead of `NaN`.
916
+ - S10 `Knob` and `ChipGroup`: the instrument's controls, registered with `data-control` so the gate can check
917
+ that each one changes the state key.
918
+ - S11 `SHELL_TOKENS`, `resolveToken` and `useThemeTokens`: the colour tokens the shell defines, resolved to
919
+ concrete values for canvas and WebGL; `validateArchitectureConfig` checks the modal on mount (at least five
920
+ tabs, inline SVG strings only, only defined tokens, no hex colours, both languages).
921
+ - S12 `UPlotChart` from the separate entry `@fasl-work/caos-app-shell/chart` (with `chart.css`): sized by its
922
+ container, colours resolved from the theme and rebuilt on theme or language change, options compared by
923
+ value, null-safe localised ticks, a y axis sized to its widest label, a cursor readout row instead of the
924
+ clipped legend, and labelled marks. `uplot` is an optional peer dependency.
925
+ - S13 `Stage` and `useStageSize`: an instrument draws only once its box has a size, declares
926
+ `data-drawn`, `data-width` and `data-height`, and reports a stage still at zero size after 1.5 s.
927
+ - S17 `reserved-classes.json`: every class the stylesheet styles, generated at build, for the product guard
928
+ that forbids redefining them.
929
+ - S18 `AppShell` reports a missing router context and names the duplicated-React cause.
930
+ - `@fasl-work/caos-app-shell/keys`: the storage keys (`caos.theme`, `caos.lang`) for the gate and the boot
931
+ script.
932
+
933
+ - The instrument's height chain (failure class 4): `PlotCard fill` takes the height its panel leaves, `.caos-views-row`
934
+ sets filling views side by side, sub-tabs inside a panel pass the height down, and `UPlotChart height="fill"`
935
+ takes its container's height; below 900 px a filling view gets a fixed readable height. Before this, a `Stage`
936
+ inside a `PlotCard` resolved to zero height and never drew.
937
+ - `.caos-table`: tables in views and documents (tabular figures, a header that stays in view).
938
+ - `UPlotChart` series `mode: 'points'` (a scatter of cases over a curve); `.caos-pending` for a view whose data is not
939
+ there yet (it declares `data-state="loading"`); the `BiText` type is exported.
940
+ - `caos-shell-gate` rebuilt as `gate/` (G1 to G9 of the 2026-10-04 history): subject and mode identity with the
941
+ exported storage keys; console, page and HTTP errors; deep links with and without a trailing slash, artifacts
942
+ answered as JSON and a missing asset answered 404, through a built-in server that answers as GitHub Pages does
943
+ (`--serve dist`); boxes inside the viewport and the rail, containers that cut or scroll sideways, truncated text
944
+ without its full text, every control reached by the pointer the way a reader can scroll to it; painted pixels per
945
+ drawing stage and the drawn views covering half the viewport, measured on the screenshot; every route, tab,
946
+ sub-tab and case at 390x844, 768x1024, 1280x800, 1600x900 and 2560x1440, both themes and both languages, waiting
947
+ for declared state; animation frames and mutations at rest; every registered control changing the selection key,
948
+ and the rendered case being the case asked for, by selection and by deep link.
949
+ - G15: the gate's self-test (`npm run test:gate`, a CI job): a fixture product built on this shell passes the full
950
+ matrix clean, and each of 26 planted defects fails the check that owns it.
951
+
952
+ ### Changed
953
+
954
+ - `CaseWorkbench` is the whole App route: it renders the layout, puts the case picker (`cases`), the variants and
955
+ the product's `rail` in the rail, and provides the selection key to the rail and the instrument. The per-group
956
+ badge row is gone (each view shows its own lane and provenance); `WorkbenchLayout` takes a `railHead`. The
957
+ workbench spacing is tightened (rail `clamp(248px, 19vw, 340px)`, compact group and sub-tab rows), so the drawn
958
+ views cover half of a 1280x800 viewport.
959
+ - The variant row is a registered control (`data-control="variant"`).
960
+ - `ShellConfig.license` and `ShellConfig.visibility` are required (S5): the footer shows the product's own
961
+ licence with no default, and a private product shows no source link. `ShellConfig.build` adds the build id to
962
+ the footer beside the version, which must be `X.XX.XXX` (S6); both are written as `data-version` and
963
+ `data-build`.
964
+ - `Readout` items take a numeric `value` (or `null`) with a unit or `unitless: true` and are formatted by the
965
+ shell; `lane` and `provenance` are required on `Readout` and `PlotCard`.
966
+ - `CaseSelector` shows each case by name only (the id is written as `data-case`).
967
+ - The architecture modal takes inline SVG strings only; the URL fetch and cache are removed.
968
+
969
+ ### Fixed
970
+
971
+ - The document could not scroll on a page taller than the viewport (`html, body, #root { height: 100% }`); it is
972
+ now `height: auto; min-height: 100%` (S1).
973
+ - `[hidden]` lost to component `display` rules and showed hidden panels; it now always hides (S2). Each theme
974
+ declares `color-scheme`, so native scrollbars and form controls follow it.
975
+ - The header navigation pushed the actions off a narrow screen; it shrinks and scrolls at every width.
976
+ Native selects and inputs follow the theme, and sub-tab rows stay on one row.
977
+ - A partly hidden item focused by keyboard in a scrolling row (nav, tab rows, phone footer) is scrolled fully into
978
+ view; the routed nav re-centres its active link on every route change. The contained phone footer stays one row
979
+ with a faded end instead of wrapping into three lines over the instrument.
980
+ - Badges never wrap mid-label; stacked views in a panel keep a gap.
981
+ - A scrolling row reveals the reader's focused item even when a route change re-runs its effect afterwards (the
982
+ late effect had scrolled a focused link back out of view; the navigation e2e caught it as a flaky failure), and
983
+ the reveal is instant.
984
+ - Units are bilingual where they are words (`unit: { en: 'people', es: 'personas' }`) in `Readout`, `Gauge`, `Knob` and the
985
+ chart axes; a Spanish reader no longer meets an English unit. `formatNumber` shows every integer digit of a count
986
+ (27,345, not 27,350); significant digits round only the fraction. `Verdict compact` puts the title and the verdict
987
+ on one row, for a verdict above a drawing.
988
+ - Found by the gate on the product template: `CaseSelector` resolves a `?case=` deep link once an asynchronous case
989
+ list arrives (it adopted on mount only, and wrote the default case into the URL first), and its modified badge
990
+ names the case instead of its id and wraps in the rail; the architecture validation no longer reads a marker
991
+ reference (`url(#dc5-arrow)`) as a hex colour; the gate's reach check tests a fragment of a wrapped inline link,
992
+ not the empty centre of its box. The fixture now loads its cases asynchronously and carries a wrapped link.
993
+
994
+ - `ChromeStrings` typed each value as `string`; with `as const` the English literals made the Spanish table
995
+ unassignable and `tsc` 5.9 failed, which also failed the build that `npm ci` runs.
996
+
997
+ ## [0.06.013] - 2026-09-26
998
+
999
+ ### Added
1000
+
1001
+ - `publish-npm.yml`: trusted publishing to npm from a GitHub release (OIDC, provenance attached, no token stored).
1002
+ npm no longer issues 2FA-bypass tokens, which is why 0.6.9 to 0.6.12 never reached the registry; this release
1003
+ is the first published since 0.6.8 and carries everything those four entries describe.
1004
+
1005
+ ## [0.06.012] - 2026-09-25
1006
+
1007
+ ### Added
1008
+
1009
+ - Continuous integration, for the first time in this package: build, the node test suite, and the
1010
+ guards every product repo already carries (no tracked `.env` or `dist/`, no leaked local path,
1011
+ no em-dash or emoji per ADR-0067, the ADR-0074 CI budget gate, and version coherence per
1012
+ ADR-0068). Trunk-only triggers, a concurrency group, a timeout per job.
1013
+
1014
+ ### Fixed
1015
+
1016
+ - `VERSION` had stayed at `0.06.008` through three tagged releases while `package.json` moved to
1017
+ `0.6.11`, and `0.06.010` and `0.06.011` had no changelog entry. All four sources now agree, and the
1018
+ two entries below are transcribed from the commits they tag.
1019
+ - Twenty-two em-dashes in comments, docstrings, the README, this changelog and one UI string
1020
+ (`CaseSelector.lockedNote`), replaced per ADR-0067.
1021
+
1022
+ ## [0.06.011] - 2026-09-24
1023
+
1024
+ ### Fixed
1025
+
1026
+ - On a phone viewport the focus HUD no longer overflows: below 860px it is a four-column grid with
1027
+ ellipsised labels and compact value text instead of a horizontally scrolling flex row.
1028
+
1029
+ ## [0.06.010] - 2026-09-24
1030
+
1031
+ ### Added
1032
+
1033
+ - `FocusShell`, the shell-owned full-viewport scenario focus frame (ADR-0070): the product supplies
1034
+ its instrument (`stage`), its controls (`rail`), a title, a description and HUD readouts; the shell
1035
+ owns the layout, the language and theme toggles, the exit action, and the phone breakpoint that
1036
+ stacks stage over rail. Exported from the package barrel with its props type and a contract test.
1037
+
1038
+ ## [0.06.009] - 2026-09-24
1039
+
1040
+ ### Fixed
1041
+
1042
+ - Keep route navigation in the same compact header row on narrow screens, with keyboard-accessible horizontal route scrolling and no document overflow.
1043
+ - Keep complete mobile footer provenance accessible in one horizontally scrollable row instead of consuming instrument height.
1044
+
1045
+ ### Changed
1046
+
1047
+ - Build the distributable during Git dependency installation, so a pinned source revision can be consumed while registry publication is unavailable.
1048
+
1049
+ ## [0.06.006] - 2026-09-10
1050
+
1051
+ ### Fixed
1052
+
1053
+ - The repository link now uses the supported `CodeXml` icon. Lucide 1.x removed the `Github` export, which broke consumer browser builds despite satisfying the shell's declared peer range. The accessible repository link and destination remain intact.
1054
+ - Development validation now uses Lucide 1.43.0, with a browser-bundle regression test that resolves its actual ESM exports instead of externalizing the dependency.
1055
+
1056
+ ## [0.06.005] - 2026-09-10
1057
+
1058
+ ### Added
1059
+
1060
+ - Optional bilingual `footer.attribution` (or `false`) and `footer.license` let applications use their actual authorship/privacy requirements and code license while retaining the shared shell. Existing consumers keep their previous footer by default.
1061
+
1062
+ ## [0.06.004] - 2026-09-06
1063
+
1064
+ ### Fixed
1065
+
1066
+ - **A wide table in a vertical sub-tab panel pushed the page past the viewport.** The
1067
+ `.subtabs-vertical` grid used a bare `1fr` panel track, which is `minmax(auto, 1fr)`: the
1068
+ panel's min-content width is the table's width, so the grid sized itself to the table
1069
+ instead of letting the table scroll inside its wrapper (ADR-0071 rule 3). Measured on
1070
+ Porvenir's Datasets tab at 1280x800: the panel column rendered 1380px wide on a 1280px
1071
+ viewport. The track is now `minmax(0, 1fr)`, and `.subtabs`, `.subtabpanels` and
1072
+ `.subtabpanel` carry `min-width: 0` beside `.tabs` and `.tabpanel`.
1073
+ - **On a contained surface (`.app-shell.fixed`) the footer took 178px.** A product's
1074
+ provenance and disclaimer wrapped into five lines at 1600x900 and the instrument fell from
1075
+ 56 to 42 percent of the viewport, under the ADR-0071 rule 8 floor. The footer keeps its
1076
+ text (ADR-0016 s2) and is set compact on fixed routes only: two to three lines.
1077
+
1078
+ ## [0.06.003] - 2026-09-06
1079
+
1080
+ ### Added
1081
+
1082
+ - **`ShellConfig.fixedRoutes` and `ShellConfig.fixed`**, the per-route opt-in to the contained
1083
+ layout ADR-0071 rule 1 requires (the app surface IS the viewport; the one container that
1084
+ owns long content scrolls, the document never does). `styles.css` has carried
1085
+ `.app-shell.fixed` since the containment fix with the note "apps that fill the viewport add
1086
+ it", and no app could: `AppShell` rendered a fixed class string. Measured on Porvenir before
1087
+ this field existed: every tab of the App route scrolled the document by 200 to 770px at
1088
+ 1600x900, and the ADR gate could not see it because it never measured scrollHeight. A route
1089
+ in `fixedRoutes` (exact for `/`, prefix for any other path) gets the class; `fixed: true`
1090
+ contains every route of a single-surface app. Nothing changes for consumers that set neither.
1091
+
1092
+ ### Fixed
1093
+
1094
+ - **`test/layout-primitives.test.ts` could not see a compound or descendant override.** It
1095
+ inspected only blocks whose selector list contained exactly `.page-body`; appending
1096
+ `.app-shell .page-body { max-width: 100%; margin-inline: 0 }` to the stylesheet removed the
1097
+ reading cap and the centering from every product and all five tests passed. The test now
1098
+ walks every rule whose subject carries the class, requires the plain rule to hold the
1099
+ intended value, and refuses any competing declaration outside the documented `.wide`
1100
+ opt-in; proven on that exact override (two failures) and clean on the shipped stylesheet.
1101
+
1102
+ ## [0.06.002] - 2026-09-03
1103
+
1104
+ ### Added
1105
+
1106
+ - **`CaseSelector layout="select"`**, the compact one-of-N picker ADR-0071 rule 7 asks for:
1107
+ a native dropdown with one `optgroup` per category, preserving the category structure the
1108
+ chip layout carries. The chip layout spends vertical space linearly in the number of cases,
1109
+ and that space comes out of the instrument on every render. Measured on Porvenir at
1110
+ 2560x1440: ten cases in four categories occupied a 308px block, 21 percent of the viewport,
1111
+ for a choice one control expresses. Default stays `chips`, so nothing changes for existing
1112
+ consumers until they opt in.
1113
+
1114
+ ## [0.06.001] - 2026-09-03
1115
+
1116
+ ### Fixed
1117
+
1118
+ - **Every prose page in every product on this shell rendered full-bleed.** `styles.css` defined
1119
+ `.page-body { max-width: var(--maxw) }`, the 1200px reading measure ADR-0017 s1.1 specifies, and
1120
+ then 216 lines later a containment fix added a second unscoped `.page-body { max-width: 100% }`.
1121
+ CSS takes the last one, so the cap was silently removed everywhere. Measured on Porvenir at
1122
+ 1600x900: the doc routes rendered 1600px wide instead of 1200px centered, so body text ran the
1123
+ full width of the display.
1124
+
1125
+ The overriding rule was itself written to fix a real bug (a `nowrap` row sizing its whole column
1126
+ and pushing the page wider than the viewport, ADR-0071 rule 3). Containment is about letting a box
1127
+ SHRINK below its content, which is `min-width: 0`; capping it at 100% does nothing for that and
1128
+ costs the measure. Changed to `min-width: 0`. `.page-body.wide` is more specific and still wins,
1129
+ so workbench routes keep the full viewport.
1130
+
1131
+ Nothing could see this. The package built, the types were correct, and every consumer imported the
1132
+ right class. `test/layout-primitives.test.ts` now asserts that the LAST declaration of a capped
1133
+ property on each layout primitive is the intended one, so a later rule overriding a primitive fails
1134
+ here instead of shipping to every app. Verified non-vacuous: re-adding the rule fails the test.
1135
+
1136
+ ## [0.06.000] - 2026-08-23
1137
+
1138
+ ### Added
1139
+
1140
+ - **Bilingual architecture diagrams (ADR-0058).** The ArchitectureModal panel now carries
1141
+ `data-arch-lang`, and the stylesheet ships the three rules that act on it. A diagram tags each
1142
+ translatable `<text>` twice at the same coordinates, `class="... l-en"` and `class="... l-es"`,
1143
+ and exactly one is shown. Diagrams previously rendered English text inside a fully Spanish UI,
1144
+ which was the last untranslated surface in an otherwise bilingual shell.
1145
+
1146
+ ONE file carries both languages on purpose. Two files would be two things to keep in step, and
1147
+ the one not on screen is the one that goes stale. Anything language-neutral, a number, a file
1148
+ name, an identifier, needs no pair.
1149
+
1150
+ Backwards compatible: a diagram with no `l-en`/`l-es` classes renders exactly as before.
1151
+
1152
+ ### Fixed
1153
+
1154
+ - `npm test` listed its test files by hand, so a newly added test file silently never ran. It now
1155
+ discovers `test/**/*.test.ts(x)`, which immediately picked up two tests that had been invisible.
1156
+
1157
+ ## [0.05.000] - 2026-08-03
1158
+
1159
+ ### Added
1160
+
1161
+ - `WorkbenchShell`, a typed authenticated-console frame with shared route rendering, desktop sidebar,
1162
+ mobile bottom navigation and extension slots for product-owned brand, sidebar trust/actions, command
1163
+ surface, account/chat controls, overlays and canonical content.
1164
+ - A server-rendered contract test proving route activation, slot preservation, the main-content
1165
+ landmark and overlay placement without coupling the package to product state.
1166
+
1167
+ ### Changed
1168
+
1169
+ - The shell now consumes the stable `react-router` core peer contract across major versions 6, 7 and
1170
+ 8. Browser applications can keep `react-router-dom` 6/7, while core-only Router 8 applications are
1171
+ supported without a downgrade. The package is built/tested against 8.3.0 and retains `AppShell`.
1172
+ - Package, display version, lockfile, README and changelog now advance together for the npm release.
1173
+
1174
+ ## [0.04.000] - 2026-07-28
1175
+
1176
+ ### Added (the ADR-0071 UI floor, so every product inherits it instead of fixing it alone)
1177
+ - **`.page-body.wide`**: a workbench is not prose. App surfaces take the full viewport; the 1200px
1178
+ reading measure (`--maxw`) stays for prose pages. Capping an instrument at a reading width discarded
1179
+ 400px on a 1600px display and over half of a 2560px one, which is why the visualization read as a
1180
+ thumbnail across the whole product line.
1181
+ - **`.app-shell.fixed`**: an app surface sized by flex rather than by a hardcoded guess at the chrome.
1182
+ Products were computing `calc(100dvh - 150px)` while real chrome measured 175px, leaving a few pixels
1183
+ of scroll; the constant also has to be maintained whenever the header or footer changes.
1184
+ - **`.app-shell.fixed .site-footer { margin-top: 0 }`**: the prose footer margin (3rem) is dead space in
1185
+ a viewport-filling app. It was exactly the 48px gap users saw above the footer.
1186
+
1187
+ ### Changed
1188
+ - **The tab bar is a single row.** `flex-wrap: wrap` let a 12-to-18 tab bar occupy two and three rows,
1189
+ and every extra row is vertical space taken from the content permanently, on every render.
1190
+ - **Layout containment.** `html, body { overflow-x: hidden }` plus `min-width: 0` and `max-width: 100%`
1191
+ on `.tabs`, `.tabpanel` and `.tablist`. A flex or grid item defaults to `min-width: auto` and will not
1192
+ shrink below its content, so a single `nowrap` row silently sized the page to 1817px on a 1600px
1193
+ viewport and the user had to drag sideways to reach the right edge.
1194
+
1195
+ ### Note for consumers
1196
+ A tab row that must scroll horizontally MUST NOT use `overflow-x` on the row itself if it hosts dropdown
1197
+ menus: in CSS a box cannot keep `overflow-y: visible` when the other axis is anything else, so the
1198
+ declared value computes to `auto` and the row clips its own menu. Either keep the row short enough not to
1199
+ scroll (group the tabs, per ADR-0071) or position the menu `fixed`.
1200
+
1201
+ # Changelog
1202
+
1203
+ All notable changes to this product. Format: `X.XX.XXX` (display, see the workspace `versioning.md`); stays `0.x` while pre-1.0. Tag every release.
1204
+
1205
+ ## [0.03.000] · 2026-07-04
1206
+
1207
+ ### Added
1208
+ - **`usePausedViz` + `createVizLoop`**, a no-compute-bomb animation loop for canvases/3D views:
1209
+ default paused, run-once-then-stop (looping opt-in), optional `durationMs` hard cap, and auto-halt
1210
+ on a hidden tab (visibilitychange). The state machine (`createVizLoop`) is framework-free with
1211
+ injected `requestAnimationFrame`/`cancelAnimationFrame`, unit-tested with a fake clock (10 tests).
1212
+ `usePausedViz` is the React wrapper. Animated views should mount through it instead of calling rAF
1213
+ directly. Enforces the portfolio "no autoplay, no compute bomb" rule at the shell level.
1214
+ - **`CaseSelector` v2**, shared source + case picker. Chips show `ID · name`; cases render in
1215
+ labelled category groups; an optional first-level `Synthetic | Real | Uploaded` source control
1216
+ filters the deck and shows a locked-knobs explanation on non-synthetic lanes; a "modified from CASE"
1217
+ divergence badge with reset; opt-in `?case=` deep-linking. Pure model (`caseModel.ts`) unit-tested
1218
+ (6 tests). Closes the inherited selector defects portfolio-wide (deep-review 1.6.1-1.6.4).
1219
+ - Establishes the shell's first test harness (`npm test` → `node --test` + `tsx`; 16 tests).
1220
+
1221
+ ## [0.02.000] · 2026-07-03
1222
+
1223
+ ### Added
1224
+ - Adopt the `X.XX.XXX` versioning scheme: a `VERSION` file as the single source of truth, this `CHANGELOG`, and the first git tag. Baseline documenting the current shipped state; later changes are versioned by nature (major/minor/patch).
1225
+ ## [0.06.007] - 2026-09-10
1226
+
1227
+ ### Fixed
1228
+
1229
+ - Architecture dialogs keep keyboard focus within the modal and return it to the opener on close. Tabs support arrow keys, Home and End with explicit tab-panel relationships.
1230
+ - Architecture diagrams offer a contained native-size reading view and a fit toggle in English and Spanish. Native-size viewing preserves authored text size on mobile while retaining the selected theme and language.
1231
+ - A real Chromium consumer checks keyboard navigation, Escape, focus restoration, native-size scrolling and diagram fit in both languages and themes at mobile and desktop widths (`npm run test:browser`).
1232
+ ## [0.06.008] - 2026-09-10
1233
+
1234
+ ### Fixed
1235
+
1236
+ - All configured page routes remain reachable on mobile through a compact second header row. Links scroll horizontally inside the header, retain their active-page semantics and remain visible when reached by keyboard focus.
1237
+ - Real-browser navigation tests exercise all six routes by click and keyboard at 320, 390 and 1440 px, in EN/ES and both themes. They verify viewport containment and a header below 100 px so the main instrument retains most of the viewport.