@fasl-work/caos-app-shell 0.23.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/VERSION +1 -1
- package/dist/index.d.ts +23 -4
- package/dist/index.js +116 -18
- package/gate/inpage.mjs +2 -0
- package/gate/run.mjs +59 -19
- package/package.json +1 -1
- package/reserved-classes.json +4 -0
- package/styles.css +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [0.24.000] - 2026-11-08
|
|
2
|
+
|
|
3
|
+
Four defects that an ADR-0082 review of CAOS_ChancaDEM on 0.23.2 found (CAOS_MANAGE `wip/chancadem/viz-review-2026-11-07.md`, findings M11, M1, M3, m4).
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- A scene callout no longer shows its state by colour alone (known defect 58, SHW-059, ADR-0082 rule 8). Every `SceneCallouts` card carries a state label, a word in the page language with a glyph of its own shape (en normal, warning, alarm, selected, inactive; es normal, aviso, alarma, seleccionado, inactivo), marked `data-callout-state-label`, on a row of its own; the border colour stays. A callout without a recognised state gets no label (G21 already fails it). The eyebrow wraps instead of ending in an ellipsis: on the shell's own fixture the Spanish eyebrows were cut by 19 to 58 px at 390 and 1280 px.
|
|
8
|
+
- G21 measures the callouts at every size, theme and language of the gate matrix (known defect 59, SHG-090). Its callout checks (value and unit, state and state label, provenance, the 8 px clearance of the own anchor, no overlap, inside the stage, one leader per callout, no horizontal overflow) ran at the primary size, first theme and first language only, so ChancaDEM's cards overlapped by 2 to 6 px at 390x844 and in Spanish while the gate passed. The interaction, motion and performance checks stay at the primary size. The report lists the measured contexts (`storyContexts`). A new G21 check fails a callout whose state label is not visible (SHG-091).
|
|
9
|
+
- Header callouts are laid out on their measured heights, in the order of their anchors (known defect 60, SHW-060, SHW-061). `layoutCalloutHeader` placed the cards in declaration order on nominal heights (68 px, 54 px compact), so a card that grew when its source wrapped covered the next row and a card anchored at the right could sit at the left with its leader across the subject. The layer now measures its cards (ResizeObserver) and lays them out again when a height changes; `layoutCalloutHeader(items, width, height, compact, heights?)` reads the cards in anchor x order (stable for equal x), sizes each row by its tallest card, takes the slot order with the fewest crossing leaders (the anchor order winning every tie) and narrows a compact card to the cell, never over its neighbour. `layoutSceneCallouts` takes the same optional `heights`.
|
|
10
|
+
- At a phone width every verb of the story stepper is whole (SHW-062, review m4). A step is at least as wide as its number and verb and never wider than the row (a longer verb wraps inside its step, so a platform with a wider system font shows it whole too: the first candidate's main CI on Linux overflowed a 320 px row), the row scrolls on x, and `StoryFrame` brings the active step inside the row whenever it changes (keyboard, autoplay, deep link).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `SceneCallouts` `onLayout?: (info: { bandHeight: number }) => void` and the CSS custom property `--caos-callout-band` (px) on the header layer: the distance from the top of the layer to the bottom of the lowest card after layout.
|
|
15
|
+
|
|
16
|
+
### Adoption
|
|
17
|
+
|
|
18
|
+
- Known defects 58 and 59: no product change beyond the product's own gate run on this release; a product that drew its own state word or glyph in the eyebrow to work around defect 58 removes it. A product whose cards overlapped only on a phone or in Spanish now fails G21 there and fixes its layout.
|
|
19
|
+
- Known defect 60: a product that reserves a header band reads `--caos-callout-band` (CSS) or `onLayout` (JavaScript) instead of a band computed from the nominal 184x68 or 164x54 cards, and drops any local reordering of its callouts by anchor x.
|
|
20
|
+
- The phone stepper: no product change; a product that shortened its step verbs to fit the 90 px columns may restore them.
|
|
21
|
+
|
|
1
22
|
## [0.23.002] - 2026-11-07
|
|
2
23
|
|
|
3
24
|
### Fixed
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.24.000
|
package/dist/index.d.ts
CHANGED
|
@@ -1451,13 +1451,32 @@ interface CalloutBox {
|
|
|
1451
1451
|
anchorX: number;
|
|
1452
1452
|
anchorY: number;
|
|
1453
1453
|
}
|
|
1454
|
+
/** Measured card heights in CSS pixels by callout id; a callout without one is laid out at the nominal height. */
|
|
1455
|
+
type CalloutHeights = Readonly<Record<string, number>>;
|
|
1454
1456
|
/** Deterministic callout placement, exported so a product can test its scene geometry before rendering. */
|
|
1455
|
-
declare function layoutSceneCallouts(items: SceneCalloutDef[], width: number, height: number, compact?: boolean): CalloutBox[];
|
|
1456
|
-
/**
|
|
1457
|
-
|
|
1457
|
+
declare function layoutSceneCallouts(items: SceneCalloutDef[], width: number, height: number, compact?: boolean, heights?: CalloutHeights): CalloutBox[];
|
|
1458
|
+
/**
|
|
1459
|
+
* Callout cards in a reserved top band; leaders retain their individual scene anchors. The cards are read in the order of
|
|
1460
|
+
* their anchors' x (stable for equal x) and placed in rows from left to right, each row as tall as its tallest card, so a card
|
|
1461
|
+
* that grew when its text wrapped never covers the next row (known defect 60). `heights` are the measured card heights
|
|
1462
|
+
* (SceneCallouts measures its cards and lays them out again when one changes); without them the nominal heights are used.
|
|
1463
|
+
* When that order would make two leaders cross (anchors at different depths), the slot order with the fewest crossings is
|
|
1464
|
+
* taken instead, the anchor order winning every tie. The boxes are returned in anchor x order.
|
|
1465
|
+
*/
|
|
1466
|
+
declare function layoutCalloutHeader(items: SceneCalloutDef[], width: number, height: number, compact?: boolean, heights?: CalloutHeights): CalloutBox[];
|
|
1467
|
+
/**
|
|
1468
|
+
* Overlay anchored value labels on a stage; normalized anchors are reprojected by the product as the camera moves.
|
|
1469
|
+
* The layer measures its cards and lays them out on their real heights. In the header placement it sets
|
|
1470
|
+
* `--caos-callout-band` (px) on the layer and calls `onLayout` with the band height: the distance from the top of the
|
|
1471
|
+
* layer to the bottom of the lowest card, which a product reserves above its drawing instead of a nominal band.
|
|
1472
|
+
*/
|
|
1473
|
+
declare function SceneCallouts({ items, className, placement, onLayout }: {
|
|
1458
1474
|
items: SceneCalloutDef[];
|
|
1459
1475
|
className?: string;
|
|
1460
1476
|
placement?: 'anchored' | 'header';
|
|
1477
|
+
onLayout?: (info: {
|
|
1478
|
+
bandHeight: number;
|
|
1479
|
+
}) => void;
|
|
1461
1480
|
}): react.JSX.Element;
|
|
1462
1481
|
|
|
1463
1482
|
interface Scene3DFrameProps {
|
|
@@ -1587,4 +1606,4 @@ declare function explodedOffsets(parts: ExplodedPart[], scale?: number): Record<
|
|
|
1587
1606
|
declare function detailTier(distance: number): DetailTier;
|
|
1588
1607
|
declare function sceneEnvironment(theme: 'light' | 'dark'): SceneEnvironment;
|
|
1589
1608
|
|
|
1590
|
-
export { BREAKPOINTS, BarChart, type BarChartProps, type BarDatum, BiText, Callout, type CalloutBox, type CalloutState, type CameraFlyOptions, type CameraFrame, type CameraPose, CardDeck, type CardDeckProps, type CaseDef, type CaseFocusConfig, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, CaseWorkbench, type CaseWorkbenchProps, ChipGroup, type ChipGroupProps, type ChipOption, type Citation, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, type DeckCard, type DetailTier, DocPage, type DocPageProps, DocReferences, DocRefsScope, DocSection, type DocSectionProps, EXECUTIVE_SCHEMA_VERSION, Equation, type ExecutiveData, type ExecutiveProblem, ExecutiveView, type ExecutiveViewProps, type ExplodedPart, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, type FlipCardProps, type FocusMode, type FocusModeConfig, type FocusModeOptions, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, Glossary, type GlossaryEntry, InlineMath, Knob, type KnobProps, Landing, type Lane, LaneBadge, Lang, LanguageToggle, LinkedViewProvider, type LinkedViewSide, type LinkedViewState, type LinkedViewStore, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, type MotionOptions, type MotionStage, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, Question, type QuestionProps, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Scene3DFrame, type Scene3DFrameProps, type SceneBounds, type SceneCalloutDef, SceneCallouts, type SceneEnvironment, type SelectionOrigin, Stage, type StageSize, Stat, type StatProps, type StoryAccent, StoryFrame, type StoryFrameProps, type StoryStep, type SubTabDef, SubTabs, type SubTabsProps, type SurfaceFocusConfig, SurfacePage, type SurfacePageProps, type TabDef, type TabGroupDef, TabGroups, type TabGroupsProps, Tabs, type TabsProps, type Theme, ThemeToggle, type Tone, type UsePausedVizOptions, VariantBar, type VariantBarProps, type VariantDef, type Vec3, Verdict, type VerdictProps, type ViewFocusConfig, ViewsRow, type ViewsRowProps, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchGroup, WorkbenchLayout, type WorkbenchLayoutProps, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, type WorkbenchState, WorkbenchStateContext, applyTheme, cameraForBounds, caseKind, caseTooltip, casesInSource, createLinkedViewStore, createVizLoop, deckPosition, deckWindow, detailTier, explodedOffsets, firstAppearance, flowSpeed, flyCamera, groupByCategory, isStale, layoutSceneCallouts, makeStateKey, perPageFor, prefersReducedMotion, readCaseParam, readTheme, removeQueryParam, replaceQueryParam, runStagedTransition, sceneEnvironment, sourcesPresent, useFocusMode, useLinkedCursor, useLinkedItem, useLinkedView, usePausedViz, useStageSize, useThemeStore, useUrlView, useWorkbenchState, validateExecutive, viewParamOf, withCaseParam };
|
|
1609
|
+
export { BREAKPOINTS, BarChart, type BarChartProps, type BarDatum, BiText, Callout, type CalloutBox, type CalloutHeights, type CalloutState, type CameraFlyOptions, type CameraFrame, type CameraPose, CardDeck, type CardDeckProps, type CaseDef, type CaseFocusConfig, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, CaseWorkbench, type CaseWorkbenchProps, ChipGroup, type ChipGroupProps, type ChipOption, type Citation, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, type DeckCard, type DetailTier, DocPage, type DocPageProps, DocReferences, DocRefsScope, DocSection, type DocSectionProps, EXECUTIVE_SCHEMA_VERSION, Equation, type ExecutiveData, type ExecutiveProblem, ExecutiveView, type ExecutiveViewProps, type ExplodedPart, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, type FlipCardProps, type FocusMode, type FocusModeConfig, type FocusModeOptions, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, Glossary, type GlossaryEntry, InlineMath, Knob, type KnobProps, Landing, type Lane, LaneBadge, Lang, LanguageToggle, LinkedViewProvider, type LinkedViewSide, type LinkedViewState, type LinkedViewStore, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, type MotionOptions, type MotionStage, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, Question, type QuestionProps, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Scene3DFrame, type Scene3DFrameProps, type SceneBounds, type SceneCalloutDef, SceneCallouts, type SceneEnvironment, type SelectionOrigin, Stage, type StageSize, Stat, type StatProps, type StoryAccent, StoryFrame, type StoryFrameProps, type StoryStep, type SubTabDef, SubTabs, type SubTabsProps, type SurfaceFocusConfig, SurfacePage, type SurfacePageProps, type TabDef, type TabGroupDef, TabGroups, type TabGroupsProps, Tabs, type TabsProps, type Theme, ThemeToggle, type Tone, type UsePausedVizOptions, VariantBar, type VariantBarProps, type VariantDef, type Vec3, Verdict, type VerdictProps, type ViewFocusConfig, ViewsRow, type ViewsRowProps, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchGroup, WorkbenchLayout, type WorkbenchLayoutProps, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, type WorkbenchState, WorkbenchStateContext, applyTheme, cameraForBounds, caseKind, caseTooltip, casesInSource, createLinkedViewStore, createVizLoop, deckPosition, deckWindow, detailTier, explodedOffsets, firstAppearance, flowSpeed, flyCamera, groupByCategory, isStale, layoutCalloutHeader, layoutSceneCallouts, makeStateKey, perPageFor, prefersReducedMotion, readCaseParam, readTheme, removeQueryParam, replaceQueryParam, runStagedTransition, sceneEnvironment, sourcesPresent, useFocusMode, useLinkedCursor, useLinkedItem, useLinkedView, usePausedViz, useStageSize, useThemeStore, useUrlView, useWorkbenchState, validateExecutive, viewParamOf, withCaseParam };
|
package/dist/index.js
CHANGED
|
@@ -4169,6 +4169,15 @@ function StoryFrame({ steps, activeId, onStepChange, onExit, labels = {}, autoAd
|
|
|
4169
4169
|
const activeIndex = Math.max(0, steps.findIndex((step) => step.id === activeId));
|
|
4170
4170
|
const active = steps[activeIndex];
|
|
4171
4171
|
const copy = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
|
|
4172
|
+
const stepper = useRef(null);
|
|
4173
|
+
useEffect(() => {
|
|
4174
|
+
const row = stepper.current;
|
|
4175
|
+
const link = row?.querySelector('[aria-current="step"]');
|
|
4176
|
+
if (!row || !link) return;
|
|
4177
|
+
const r = row.getBoundingClientRect(), l = link.getBoundingClientRect();
|
|
4178
|
+
if (l.left < r.left) row.scrollLeft -= r.left - l.left;
|
|
4179
|
+
else if (l.right > r.right) row.scrollLeft += Math.min(l.right - r.right, l.left - r.left);
|
|
4180
|
+
}, [activeId]);
|
|
4172
4181
|
useEffect(() => {
|
|
4173
4182
|
if (steps.length < 3 || steps.length > 6) console.error("[caos-app-shell] A showcase story has 3 to 6 steps (ADR-0082 rule 3)");
|
|
4174
4183
|
}, [steps.length]);
|
|
@@ -4232,7 +4241,7 @@ function StoryFrame({ steps, activeId, onStepChange, onExit, labels = {}, autoAd
|
|
|
4232
4241
|
}, children: pick(copy.exit, lang) })
|
|
4233
4242
|
] })
|
|
4234
4243
|
] }),
|
|
4235
|
-
/* @__PURE__ */ jsx("nav", { className: "caos-story-stepper", role: "tablist", "aria-label": pick(copy.steps, lang), children: steps.map((step) => /* @__PURE__ */ jsxs(Link, { role: "tab", "aria-selected": step.id === active.id, "aria-current": step.id === active.id ? "step" : void 0, to: step.href, "data-story-link": step.id, className: step.id === active.id ? "caos-story-step is-active" : "caos-story-step", onClick: () => {
|
|
4244
|
+
/* @__PURE__ */ jsx("nav", { ref: stepper, className: "caos-story-stepper", role: "tablist", "aria-label": pick(copy.steps, lang), children: steps.map((step) => /* @__PURE__ */ jsxs(Link, { role: "tab", "aria-selected": step.id === active.id, "aria-current": step.id === active.id ? "step" : void 0, to: step.href, "data-story-link": step.id, className: step.id === active.id ? "caos-story-step is-active" : "caos-story-step", onClick: () => {
|
|
4236
4245
|
setPlaying(false);
|
|
4237
4246
|
onStepChange(step.id);
|
|
4238
4247
|
}, children: [
|
|
@@ -4250,14 +4259,22 @@ function StoryFrame({ steps, activeId, onStepChange, onExit, labels = {}, autoAd
|
|
|
4250
4259
|
] }, active.id)
|
|
4251
4260
|
] });
|
|
4252
4261
|
}
|
|
4262
|
+
var STATE_LABELS = {
|
|
4263
|
+
alarm: { glyph: "\u25B2", en: "alarm", es: "alarma" },
|
|
4264
|
+
warning: { glyph: "\u25C6", en: "warning", es: "aviso" },
|
|
4265
|
+
normal: { glyph: "\u25CF", en: "normal", es: "normal" },
|
|
4266
|
+
selected: { glyph: "\u25CE", en: "selected", es: "seleccionado" },
|
|
4267
|
+
inactive: { glyph: "\u25CB", en: "inactive", es: "inactivo" }
|
|
4268
|
+
};
|
|
4253
4269
|
var overlaps = (a, b) => a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
|
|
4254
4270
|
var coversAnchor = (box, x, y, clearance = 8) => x >= box.left - clearance && x <= box.left + box.width + clearance && y >= box.top - clearance && y <= box.top + box.height + clearance;
|
|
4255
|
-
function layoutSceneCallouts(items, width, height, compact = false) {
|
|
4271
|
+
function layoutSceneCallouts(items, width, height, compact = false, heights) {
|
|
4256
4272
|
const w = compact ? 142 : 184;
|
|
4257
|
-
const
|
|
4273
|
+
const nominal = compact ? 56 : 68;
|
|
4258
4274
|
const margin = 8;
|
|
4259
4275
|
const boxes = [];
|
|
4260
4276
|
for (const item of items) {
|
|
4277
|
+
const h = heights?.[item.id] ?? nominal;
|
|
4261
4278
|
const x = Math.max(0, Math.min(width, item.anchor.x * width));
|
|
4262
4279
|
const y = Math.max(0, Math.min(height, item.anchor.y * height));
|
|
4263
4280
|
const d = compact ? 82 : 112;
|
|
@@ -4275,19 +4292,67 @@ function layoutSceneCallouts(items, width, height, compact = false) {
|
|
|
4275
4292
|
}
|
|
4276
4293
|
return boxes;
|
|
4277
4294
|
}
|
|
4278
|
-
function
|
|
4279
|
-
const
|
|
4280
|
-
const
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4295
|
+
function leadersCross(a, b) {
|
|
4296
|
+
const [x1, y1, x2, y2] = [a.anchorX, a.anchorY, a.left + a.width / 2, a.top + a.height / 2];
|
|
4297
|
+
const [x3, y3, x4, y4] = [b.anchorX, b.anchorY, b.left + b.width / 2, b.top + b.height / 2];
|
|
4298
|
+
const d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
|
|
4299
|
+
if (Math.abs(d) < 1e-9) return false;
|
|
4300
|
+
const t2 = ((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3)) / d;
|
|
4301
|
+
const u = ((x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1)) / d;
|
|
4302
|
+
return t2 > 1e-6 && t2 < 1 - 1e-6 && u > 1e-6 && u < 1 - 1e-6;
|
|
4303
|
+
}
|
|
4304
|
+
function* orders(n, prefix = []) {
|
|
4305
|
+
if (prefix.length === n) {
|
|
4306
|
+
yield prefix;
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
for (let i = 0; i < n; i += 1) if (!prefix.includes(i)) yield* orders(n, [...prefix, i]);
|
|
4310
|
+
}
|
|
4311
|
+
function layoutCalloutHeader(items, width, height, compact = false, heights) {
|
|
4312
|
+
const nominalW = compact ? 164 : 184, nominalH = compact ? 54 : 68, margin = 8, gap = compact ? 6 : 8;
|
|
4313
|
+
const minW = compact ? 140 : nominalW;
|
|
4314
|
+
const sorted = items.map((item, index) => ({ item, index })).sort((a, b) => a.item.anchor.x - b.item.anchor.x || a.index - b.index).map(({ item }) => item);
|
|
4315
|
+
if (!sorted.length) return [];
|
|
4316
|
+
const columns = Math.max(1, Math.min(sorted.length, Math.floor((width - margin * 2 + gap) / (minW + gap))));
|
|
4317
|
+
const place = (slots) => {
|
|
4318
|
+
const boxes = [];
|
|
4319
|
+
let top = margin;
|
|
4320
|
+
for (let start = 0; start < slots.length; start += columns) {
|
|
4321
|
+
const row = slots.slice(start, start + columns);
|
|
4322
|
+
const cell = (width - margin * 2 - gap * (row.length - 1)) / row.length;
|
|
4323
|
+
const w = Math.max(0, Math.min(nominalW, cell));
|
|
4324
|
+
const rowHeight = Math.max(...row.map((item) => heights?.[item.id] ?? nominalH));
|
|
4325
|
+
row.forEach((item, column) => boxes.push({ id: item.id, left: margin + column * (cell + gap) + (cell - w) / 2, top, width: w, height: heights?.[item.id] ?? nominalH, anchorX: item.anchor.x * width, anchorY: item.anchor.y * height }));
|
|
4326
|
+
top += rowHeight + gap;
|
|
4327
|
+
}
|
|
4328
|
+
return boxes;
|
|
4329
|
+
};
|
|
4330
|
+
const crossings = (boxes) => {
|
|
4331
|
+
let count = 0;
|
|
4332
|
+
for (let i = 0; i < boxes.length; i += 1) for (let j = i + 1; j < boxes.length; j += 1) if (leadersCross(boxes[i], boxes[j])) count += 1;
|
|
4333
|
+
return count;
|
|
4334
|
+
};
|
|
4335
|
+
let best = place(sorted);
|
|
4336
|
+
let fewest = crossings(best);
|
|
4337
|
+
if (fewest > 0 && sorted.length <= 6) {
|
|
4338
|
+
for (const order of orders(sorted.length)) {
|
|
4339
|
+
const boxes = place(order.map((i) => sorted[i]));
|
|
4340
|
+
const count = crossings(boxes);
|
|
4341
|
+
if (count < fewest) {
|
|
4342
|
+
best = boxes;
|
|
4343
|
+
fewest = count;
|
|
4344
|
+
if (count === 0) break;
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
const at = new Map(best.map((box) => [box.id, box]));
|
|
4349
|
+
return sorted.map((item) => at.get(item.id));
|
|
4286
4350
|
}
|
|
4287
|
-
function SceneCallouts({ items, className, placement = "anchored" }) {
|
|
4351
|
+
function SceneCallouts({ items, className, placement = "anchored", onLayout }) {
|
|
4288
4352
|
const lang = useShellLang();
|
|
4289
4353
|
const host = useRef(null);
|
|
4290
4354
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
4355
|
+
const [heights, setHeights] = useState({});
|
|
4291
4356
|
useEffect(() => {
|
|
4292
4357
|
const node = host.current;
|
|
4293
4358
|
if (!node) return;
|
|
@@ -4298,15 +4363,44 @@ function SceneCallouts({ items, className, placement = "anchored" }) {
|
|
|
4298
4363
|
observer.observe(node);
|
|
4299
4364
|
return () => observer.disconnect();
|
|
4300
4365
|
}, []);
|
|
4366
|
+
const ids = items.map((item) => item.id).join("\0");
|
|
4367
|
+
useEffect(() => {
|
|
4368
|
+
const node = host.current;
|
|
4369
|
+
if (!node) return;
|
|
4370
|
+
const observer = new ResizeObserver((entries) => setHeights((previous) => {
|
|
4371
|
+
let next = previous;
|
|
4372
|
+
for (const entry of entries) {
|
|
4373
|
+
const id = entry.target.dataset.caosCallout;
|
|
4374
|
+
const measured2 = entry.borderBoxSize?.[0]?.blockSize ?? entry.target.offsetHeight;
|
|
4375
|
+
if (id === void 0 || !(measured2 > 0) || Math.abs((previous[id] ?? -1) - measured2) <= 0.25) continue;
|
|
4376
|
+
if (next === previous) next = { ...previous };
|
|
4377
|
+
next[id] = measured2;
|
|
4378
|
+
}
|
|
4379
|
+
return next;
|
|
4380
|
+
}));
|
|
4381
|
+
node.querySelectorAll("[data-caos-callout]").forEach((card) => observer.observe(card));
|
|
4382
|
+
return () => observer.disconnect();
|
|
4383
|
+
}, [ids, placement]);
|
|
4384
|
+
const compact = size.width < 640;
|
|
4301
4385
|
const boxes = useMemo(() => {
|
|
4302
|
-
if (placement === "anchored") return layoutSceneCallouts(items, size.width, size.height,
|
|
4303
|
-
return layoutCalloutHeader(items, size.width, size.height,
|
|
4304
|
-
}, [items, placement, size]);
|
|
4305
|
-
|
|
4386
|
+
if (placement === "anchored") return layoutSceneCallouts(items, size.width, size.height, compact, heights);
|
|
4387
|
+
return layoutCalloutHeader(items, size.width, size.height, compact, heights);
|
|
4388
|
+
}, [items, placement, size, compact, heights]);
|
|
4389
|
+
const nominalH = placement === "anchored" ? compact ? 56 : 68 : compact ? 54 : 68;
|
|
4390
|
+
const bandHeight = placement === "header" && boxes.length ? Math.max(...boxes.map((box) => box.top + box.height)) : 0;
|
|
4391
|
+
const measured = items.every((item) => heights[item.id] !== void 0);
|
|
4392
|
+
const report = useRef(onLayout);
|
|
4393
|
+
report.current = onLayout;
|
|
4394
|
+
useEffect(() => {
|
|
4395
|
+
if (placement === "header" && size.width > 0 && measured) report.current?.({ bandHeight });
|
|
4396
|
+
}, [placement, size.width, measured, bandHeight]);
|
|
4397
|
+
const style = placement === "header" ? { "--caos-callout-band": `${bandHeight}px` } : void 0;
|
|
4398
|
+
return /* @__PURE__ */ jsxs("div", { ref: host, className: ["caos-callout-layer", className].filter(Boolean).join(" "), style, "aria-label": lang === "es" ? "Valores fijados a los componentes" : "Values anchored to components", "data-caos-callout-layer": "", "data-placement": placement, children: [
|
|
4306
4399
|
/* @__PURE__ */ jsx("svg", { className: "caos-callout-leaders", "aria-hidden": "true", viewBox: `0 0 ${Math.max(1, size.width)} ${Math.max(1, size.height)}`, preserveAspectRatio: "none", children: boxes.map((box) => /* @__PURE__ */ jsx("line", { x1: box.anchorX, y1: box.anchorY, x2: box.left + box.width / 2, y2: box.top + box.height / 2, "data-caos-callout-leader": box.id }, box.id)) }),
|
|
4307
4400
|
boxes.map((box) => {
|
|
4308
4401
|
const item = items.find((candidate) => candidate.id === box.id);
|
|
4309
|
-
|
|
4402
|
+
const state = Object.prototype.hasOwnProperty.call(STATE_LABELS, item.state) ? STATE_LABELS[item.state] : null;
|
|
4403
|
+
return /* @__PURE__ */ jsxs("article", { className: "caos-scene-callout", "data-caos-callout": item.id, "data-callout-value": item.value, "data-callout-unit": pick(item.unit, lang), "data-callout-state": item.state, "data-callout-source": pick(item.source, lang), "data-callout-anchor-x": item.anchor.x, "data-callout-anchor-y": item.anchor.y, style: { left: box.left, top: box.top, width: box.width, minHeight: nominalH }, children: [
|
|
4310
4404
|
/* @__PURE__ */ jsx("span", { className: "caos-callout-eyebrow", title: pick(item.eyebrow, lang), children: pick(item.eyebrow, lang) }),
|
|
4311
4405
|
/* @__PURE__ */ jsxs("strong", { title: `${item.value} ${pick(item.unit, lang)}`, children: [
|
|
4312
4406
|
item.value,
|
|
@@ -4314,6 +4408,10 @@ function SceneCallouts({ items, className, placement = "anchored" }) {
|
|
|
4314
4408
|
/* @__PURE__ */ jsx("small", { children: pick(item.unit, lang) })
|
|
4315
4409
|
] }),
|
|
4316
4410
|
item.trend && /* @__PURE__ */ jsx("span", { className: `caos-callout-trend trend-${item.trend}`, "aria-label": item.trend, children: item.trend === "up" ? "\u2191" : item.trend === "down" ? "\u2193" : "\u2192" }),
|
|
4411
|
+
state && /* @__PURE__ */ jsxs("span", { className: "caos-callout-state", "data-callout-state-label": "", children: [
|
|
4412
|
+
/* @__PURE__ */ jsx("span", { className: "caos-callout-state-glyph", "aria-hidden": "true", "data-callout-state-glyph": "", children: state.glyph }),
|
|
4413
|
+
state[lang]
|
|
4414
|
+
] }),
|
|
4317
4415
|
/* @__PURE__ */ jsx("small", { className: "caos-callout-source", title: pick(item.source, lang), children: pick(item.source, lang) })
|
|
4318
4416
|
] }, box.id);
|
|
4319
4417
|
})
|
|
@@ -4453,4 +4551,4 @@ function sceneEnvironment(theme) {
|
|
|
4453
4551
|
return theme === "dark" ? { background: "#101a22", ground: "#202b33", key: { color: "#fff0d5", intensity: 2.1, castShadow: true }, fill: { color: "#98c5db", intensity: 0.8 }, ambient: { color: "#c9d7df", intensity: 1.1 } } : { background: "#e9eef0", ground: "#d6dee1", key: { color: "#fff8e9", intensity: 1.7, castShadow: true }, fill: { color: "#8eabb9", intensity: 0.65 }, ambient: { color: "#ffffff", intensity: 1.35 } };
|
|
4454
4552
|
}
|
|
4455
4553
|
|
|
4456
|
-
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, LinkedViewProvider, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Scene3DFrame, SceneCallouts, Stat, StoryFrame, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, cameraForBounds, caseKind, caseTooltip, casesInSource, createLinkedViewStore, createVizLoop, deckPosition, deckWindow, detailTier, explodedOffsets, firstAppearance, flowSpeed, flyCamera, groupByCategory, isStale, layoutSceneCallouts, makeStateKey, perPageFor, prefersReducedMotion, readCaseParam, removeQueryParam, replaceQueryParam, runStagedTransition, sceneEnvironment, sourcesPresent, useFocusMode, useLinkedCursor, useLinkedItem, useLinkedView, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, validateExecutive, viewParamOf, withCaseParam };
|
|
4554
|
+
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, LinkedViewProvider, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Scene3DFrame, SceneCallouts, Stat, StoryFrame, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, cameraForBounds, caseKind, caseTooltip, casesInSource, createLinkedViewStore, createVizLoop, deckPosition, deckWindow, detailTier, explodedOffsets, firstAppearance, flowSpeed, flyCamera, groupByCategory, isStale, layoutCalloutHeader, layoutSceneCallouts, makeStateKey, perPageFor, prefersReducedMotion, readCaseParam, removeQueryParam, replaceQueryParam, runStagedTransition, sceneEnvironment, sourcesPresent, useFocusMode, useLinkedCursor, useLinkedItem, useLinkedView, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, validateExecutive, viewParamOf, withCaseParam };
|
package/gate/inpage.mjs
CHANGED
|
@@ -1583,6 +1583,8 @@ export function installLib() {
|
|
|
1583
1583
|
const callouts = [...(stage?.querySelectorAll('[data-caos-callout]') ?? [])].filter(visible).map((element) => ({
|
|
1584
1584
|
id: element.getAttribute('data-caos-callout'), value: element.getAttribute('data-callout-value'), unit: element.getAttribute('data-callout-unit'),
|
|
1585
1585
|
state: element.getAttribute('data-callout-state'), source: element.getAttribute('data-callout-source'),
|
|
1586
|
+
// the state as a reader sees it besides the colour (ADR-0082 rule 8): the visible text of the state label, empty when hidden or absent
|
|
1587
|
+
stateLabel: (() => { const label = element.querySelector('[data-callout-state-label]'); return label && visible(label) ? (label.innerText ?? label.textContent ?? '').trim() : ''; })(),
|
|
1586
1588
|
anchor: { x: Number(element.getAttribute('data-callout-anchor-x')), y: Number(element.getAttribute('data-callout-anchor-y')) }, box: box(element),
|
|
1587
1589
|
}));
|
|
1588
1590
|
const links = [...(story?.querySelectorAll('[data-story-link]') ?? [])].filter(visible).map((element) => ({ id: element.getAttribute('data-story-link'), href: element.getAttribute('href'), active: element.getAttribute('aria-selected') === 'true' }));
|
package/gate/run.mjs
CHANGED
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
// G20 chart interactions every chart is one tab stop with an accessible name, described by a table of its values and a one-line summary,
|
|
36
36
|
// and a zoomed chart resets (the + key zooms, the visible control shows, the 0 key resets); a chart that turns zoom
|
|
37
37
|
// off says why (12 letters and digits at least) and is listed among the waivers (0.19.0)
|
|
38
|
+
// G21 showcase story with --require-showcase: a directly linked 3 to 6 step story; its callouts carry value, unit, provenance and
|
|
39
|
+
// a state shown as a word (not by colour alone), leave their own anchor clear, never overlap, stay on the stage
|
|
40
|
+
// and have a leader each, at every size, theme and language of the matrix (0.24.0); interactions, motion and
|
|
41
|
+
// performance at the primary size
|
|
38
42
|
// plus the ADR-0071 and ADR-0017 measures (viewport, one-row tabs, rail without scroll, centred prose, captions,
|
|
39
43
|
// references).
|
|
40
44
|
|
|
@@ -128,6 +132,7 @@ export async function runGate(o) {
|
|
|
128
132
|
const focusReported = new Set(); // G18: an App without a Focus control is reported once per route and mode
|
|
129
133
|
const waivers = []; // ADR-0017 2.1: every waiver a documentation page declares, with its verdict
|
|
130
134
|
const docWalks = []; // the document walk's cost per state (samples and time against the budget it earned), for the report
|
|
135
|
+
const storyContexts = []; // G21: every context (size, theme, language) whose story routes had their callouts measured (0.24.0)
|
|
131
136
|
// The smallest measured values against each floor, so a pass shows its margin (and a CI font change that eats it
|
|
132
137
|
// is visible before it fails).
|
|
133
138
|
const minima = { drawnShare: null, stageFill: null };
|
|
@@ -416,12 +421,41 @@ export async function runGate(o) {
|
|
|
416
421
|
if (stories.length === 0) {
|
|
417
422
|
fail('G21', { route: '/story', mode: 'routes' }, 'no /story/<step> route is declared; ADR-0082 requires a directly addressable guided story');
|
|
418
423
|
} else {
|
|
424
|
+
const STATES = ['alarm', 'warning', 'normal', 'selected', 'inactive'];
|
|
425
|
+
// The callout checks of one story route in one context: content, state shown by more than colour, the own anchor
|
|
426
|
+
// left clear, no overlap, inside the stage, one leader each, no horizontal overflow. They run at every size, theme
|
|
427
|
+
// and language of the gate matrix (known defect 59, 0.24.0): a card grows with its text, so a layout that holds at
|
|
428
|
+
// 1280 in English can overlap on a phone or in Spanish.
|
|
429
|
+
const calloutChecks = async (page, at, f) => {
|
|
430
|
+
for (const call of f.calls) {
|
|
431
|
+
if (!call.value?.trim() || !call.unit?.trim()) fail('G21', at, `callout ${call.id} has no value or unit`);
|
|
432
|
+
if (!STATES.includes(call.state)) fail('G21', at, `callout ${call.id} has no recognised semantic state`);
|
|
433
|
+
// ADR-0082 rule 8: a state is never shown by colour alone (a word or an icon with it)
|
|
434
|
+
else if (!/[\p{L}]/u.test(call.stateLabel ?? '')) fail('G21', at, `callout ${call.id} shows its state by colour alone: no visible data-callout-state-label with its word (ADR-0082 rule 8)`);
|
|
435
|
+
if (!call.source?.trim()) fail('G21', at, `callout ${call.id} has no value provenance`);
|
|
436
|
+
if (![call.anchor?.x, call.anchor?.y].every((v) => Number.isFinite(v) && v >= 0 && v <= 1)) fail('G21', at, `callout ${call.id} has no normalized subject anchor`);
|
|
437
|
+
else if (call.box && f.stage) {
|
|
438
|
+
const x = f.stage.left + call.anchor.x * f.stage.width, y = f.stage.top + call.anchor.y * f.stage.height;
|
|
439
|
+
const clearance = 8;
|
|
440
|
+
if (x >= call.box.left - clearance && x <= call.box.right + clearance && y >= call.box.top - clearance && y <= call.box.bottom + clearance)
|
|
441
|
+
fail('G21', at, `callout ${call.id} covers its own subject anchor`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (let i = 0; i < f.calls.length; i += 1) for (let j = i + 1; j < f.calls.length; j += 1) {
|
|
445
|
+
const a = f.calls[i].box, b = f.calls[j].box;
|
|
446
|
+
if (a && b && a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top) fail('G21', at, `callouts ${f.calls[i].id} and ${f.calls[j].id} overlap`);
|
|
447
|
+
}
|
|
448
|
+
if (f.calls.some(({ box }) => !box || !f.stage || box.left < f.stage.left || box.right > f.stage.right || box.top < f.stage.top || box.bottom > f.stage.bottom)) fail('G21', at, 'one or more callouts escape the stage instead of following its subject');
|
|
449
|
+
if (f.calls.length && await page.locator('[data-story] [data-caos-callout-leader]').count() < f.calls.length) fail('G21', at, 'not every scene callout has a leader to its subject anchor');
|
|
450
|
+
if (f.scroll.width > f.viewport.width + 1) fail('G21', at, `story page overflows horizontally (${f.scroll.width}px in ${f.viewport.width}px)`);
|
|
451
|
+
};
|
|
419
452
|
const size = primarySize;
|
|
420
453
|
const [w, h] = size;
|
|
421
454
|
const mode = `${w}x${h} ${themes[0]} ${langs[0]} story`;
|
|
422
455
|
const ctx = await openContext(size, themes[0], langs[0]);
|
|
423
456
|
const { page, sink } = await newPage(ctx);
|
|
424
457
|
const visited = [];
|
|
458
|
+
storyContexts.push({ mode, routes: stories.length });
|
|
425
459
|
for (const route of stories) {
|
|
426
460
|
const at = { route, mode };
|
|
427
461
|
const res = await page.goto(urlFor(route), { waitUntil: 'load', timeout: 30000 }).catch((e) => e);
|
|
@@ -440,28 +474,33 @@ export async function runGate(o) {
|
|
|
440
474
|
if (share < 0.8) fail('G21', at, `story stage and docked panel cover ${(100 * share).toFixed(1)}% of the viewport (floor 80%, ADR-0082)`);
|
|
441
475
|
}
|
|
442
476
|
if (f.calls.length < 3) fail('G21', at, `stage has ${f.calls.length} visible anchored callouts; ADR-0082 requires at least 3`);
|
|
443
|
-
|
|
444
|
-
if (!call.value?.trim() || !call.unit?.trim()) fail('G21', at, `callout ${call.id} has no value or unit`);
|
|
445
|
-
if (!['alarm', 'warning', 'normal', 'selected', 'inactive'].includes(call.state)) fail('G21', at, `callout ${call.id} has no recognised semantic state`);
|
|
446
|
-
if (!call.source?.trim()) fail('G21', at, `callout ${call.id} has no value provenance`);
|
|
447
|
-
if (![call.anchor?.x, call.anchor?.y].every((v) => Number.isFinite(v) && v >= 0 && v <= 1)) fail('G21', at, `callout ${call.id} has no normalized subject anchor`);
|
|
448
|
-
else if (call.box && f.stage) {
|
|
449
|
-
const x = f.stage.left + call.anchor.x * f.stage.width, y = f.stage.top + call.anchor.y * f.stage.height;
|
|
450
|
-
const clearance = 8;
|
|
451
|
-
if (x >= call.box.left - clearance && x <= call.box.right + clearance && y >= call.box.top - clearance && y <= call.box.bottom + clearance)
|
|
452
|
-
fail('G21', at, `callout ${call.id} covers its own subject anchor`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
for (let i = 0; i < f.calls.length; i += 1) for (let j = i + 1; j < f.calls.length; j += 1) {
|
|
456
|
-
const a = f.calls[i].box, b = f.calls[j].box;
|
|
457
|
-
if (a && b && a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top) fail('G21', at, `callouts ${f.calls[i].id} and ${f.calls[j].id} overlap`);
|
|
458
|
-
}
|
|
459
|
-
if (f.calls.some(({ box }) => !box || box.left < f.stage.left || box.right > f.stage.right || box.top < f.stage.top || box.bottom > f.stage.bottom)) fail('G21', at, 'one or more callouts escape the stage instead of following its subject');
|
|
460
|
-
if (f.calls.length && await page.locator('[data-story] [data-caos-callout-leader]').count() < f.calls.length) fail('G21', at, 'not every scene callout has a leader to its subject anchor');
|
|
461
|
-
if (f.scroll.width > f.viewport.width + 1) fail('G21', at, `story page overflows horizontally (${f.scroll.width}px in ${f.viewport.width}px)`);
|
|
477
|
+
await calloutChecks(page, at, f);
|
|
462
478
|
visited.push({ route, step: f.step, hrefs: f.links.map((x) => x.href) });
|
|
463
479
|
drain(sink, at);
|
|
464
480
|
}
|
|
481
|
+
// Every other context of the gate matrix: the callout checks only. The performance and interaction checks below stay
|
|
482
|
+
// at the primary size. Console errors of these loads are not drained here: the main walk loads the same routes in the
|
|
483
|
+
// same contexts and reports them there once.
|
|
484
|
+
for (const s of sizes) for (const theme of themes) for (const lang of langs) {
|
|
485
|
+
if (s === primarySize && theme === themes[0] && lang === langs[0]) continue;
|
|
486
|
+
const smode = `${s[0]}x${s[1]} ${theme} ${lang} story`;
|
|
487
|
+
const sctx = await openContext(s, theme, lang);
|
|
488
|
+
const { page: sp, sink: ssink } = await newPage(sctx);
|
|
489
|
+
for (const route of stories) {
|
|
490
|
+
const at = { route, mode: smode };
|
|
491
|
+
const res = await sp.goto(urlFor(route), { waitUntil: 'load', timeout: 30000 }).catch((e) => e);
|
|
492
|
+
const status = res && typeof res.status === 'function' ? res.status() : `error ${res?.message ?? res}`;
|
|
493
|
+
if (status !== 200) { fail('G21', at, `direct story link answered ${status}`); ssink.splice(0); continue; }
|
|
494
|
+
const settled = await settle(sp);
|
|
495
|
+
if (settled) fail('G21', at, `story did not settle: ${settled}`);
|
|
496
|
+
const f = await sp.evaluate(() => window.__caosGate.showcaseFacts());
|
|
497
|
+
if (!f.present) fail('G21', at, 'route does not render [data-story]');
|
|
498
|
+
else await calloutChecks(sp, at, f);
|
|
499
|
+
ssink.splice(0);
|
|
500
|
+
}
|
|
501
|
+
storyContexts.push({ mode: smode, routes: stories.length });
|
|
502
|
+
await sctx.close();
|
|
503
|
+
}
|
|
465
504
|
const first = visited.find((x) => x.hrefs.length >= 3);
|
|
466
505
|
if (first) {
|
|
467
506
|
const hrefs = [...new Set(first.hrefs.map((href) => routeOf(new URL(href, base).href)).filter(Boolean))];
|
|
@@ -1290,6 +1329,7 @@ export async function runGate(o) {
|
|
|
1290
1329
|
minima,
|
|
1291
1330
|
waivers,
|
|
1292
1331
|
docWalks,
|
|
1332
|
+
storyContexts,
|
|
1293
1333
|
checks,
|
|
1294
1334
|
captures: captures.length,
|
|
1295
1335
|
failures,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Shared web-app shell, workbench primitives, content primitives, design system and measured gate for the CAOS / Faena public apps (ADR-0016, ADR-0017, ADR-0071).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Felipe Santibáñez-Leal",
|
package/reserved-classes.json
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"caos-callout-layer",
|
|
35
35
|
"caos-callout-leaders",
|
|
36
36
|
"caos-callout-source",
|
|
37
|
+
"caos-callout-state",
|
|
38
|
+
"caos-callout-state-glyph",
|
|
37
39
|
"caos-callout-trend",
|
|
38
40
|
"caos-card",
|
|
39
41
|
"caos-chart",
|
|
@@ -392,6 +394,8 @@
|
|
|
392
394
|
"caos-callout-layer",
|
|
393
395
|
"caos-callout-leaders",
|
|
394
396
|
"caos-callout-source",
|
|
397
|
+
"caos-callout-state",
|
|
398
|
+
"caos-callout-state-glyph",
|
|
395
399
|
"caos-callout-trend",
|
|
396
400
|
"caos-card",
|
|
397
401
|
"caos-chart",
|
package/styles.css
CHANGED
|
@@ -1025,13 +1025,18 @@ svg.lucide { flex: none; }
|
|
|
1025
1025
|
.caos-scene-callout[data-callout-state="warning"] { border-left-color: var(--color-warn); }
|
|
1026
1026
|
.caos-scene-callout[data-callout-state="normal"] { border-left-color: var(--color-good); }
|
|
1027
1027
|
.caos-scene-callout[data-callout-state="inactive"] { border-left-color: var(--color-fg-faint); }
|
|
1028
|
-
|
|
1028
|
+
/* The eyebrow wraps instead of ending in an ellipsis (0.24.0): the cards are laid out on their measured heights, so a longer
|
|
1029
|
+
Spanish eyebrow ("Alimentación del molino" in a 164 px card) is read whole and the card grows. */
|
|
1030
|
+
.caos-callout-eyebrow { min-width: 0; color: var(--color-fg); font: 700 .68rem/1.25 var(--font-mono); letter-spacing: .05em; overflow-wrap: anywhere; }
|
|
1029
1031
|
.caos-scene-callout > strong { overflow: hidden; font: 700 .92rem var(--font-mono); font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; }
|
|
1030
1032
|
.caos-scene-callout > strong small,.caos-callout-source { color: var(--color-fg); font-size: .68rem; font-weight: 500; }
|
|
1031
1033
|
.caos-callout-trend { grid-column: 2; grid-row: 1 / span 2; align-self: center; font-size: 1.1rem; font-weight: 800; }
|
|
1032
1034
|
.caos-callout-trend.trend-up { color: var(--color-bad); }
|
|
1033
1035
|
.caos-callout-trend.trend-down { color: var(--color-good); }
|
|
1034
1036
|
.caos-callout-source { grid-column: 1 / -1; }
|
|
1037
|
+
/* The state as a word with a glyph of its own shape (ADR-0082 rule 8, known defect 58): the border colour is not the only sign. */
|
|
1038
|
+
.caos-callout-state { grid-column: 1 / -1; display: flex; align-items: center; gap: .3em; min-width: 0; color: var(--color-fg); font: 700 .64rem/1.2 var(--font-mono); letter-spacing: .04em; white-space: nowrap; }
|
|
1039
|
+
.caos-callout-state-glyph { flex: none; font-size: .95em; line-height: 1; }
|
|
1035
1040
|
@media (max-width: 760px) {
|
|
1036
1041
|
.app-shell.immersive { height: auto; min-height: 100dvh; overflow: visible; }
|
|
1037
1042
|
.app-shell.immersive > .page { display: block; overflow: visible; }
|
|
@@ -1043,7 +1048,13 @@ svg.lucide { flex: none; }
|
|
|
1043
1048
|
.caos-story-actions { width: 100%; flex-wrap: wrap; justify-content: space-between; gap: .35rem; }
|
|
1044
1049
|
.caos-story-controls { flex: 1 1 auto; justify-content: flex-start; gap: .35rem; }
|
|
1045
1050
|
.caos-story button { min-height: 34px; padding: .3rem .5rem; font-size: .78rem; white-space: nowrap; }
|
|
1046
|
-
|
|
1051
|
+
/* every verb whole on a phone (review m4 of 0.23.2): a step is at least as wide as its number and verb, the row scrolls on x and
|
|
1052
|
+
StoryFrame keeps the active step inside it */
|
|
1053
|
+
.caos-story-stepper { container-type: inline-size; grid-auto-columns: minmax(max-content, 1fr); grid-auto-flow: column; grid-template-columns: unset; overflow-x: auto; }
|
|
1054
|
+
/* a step is never wider than its row (100cqi of the stepper): a verb longer than the row wraps inside it, so End always shows the
|
|
1055
|
+
last step whole, whatever width the platform's font gives the verb (Linux CI's system font overflowed a 320 px row in 0.24.0's first candidate) */
|
|
1056
|
+
.caos-story-step { max-width: 100cqi; }
|
|
1057
|
+
.caos-story-step-verb { overflow: visible; text-overflow: clip; white-space: normal; overflow-wrap: anywhere; }
|
|
1047
1058
|
.caos-story-step-headline { display: none; }
|
|
1048
1059
|
.caos-story-layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(max(46dvh, var(--caos-story-stage-minimum, 300px)), auto) auto; }
|
|
1049
1060
|
.caos-story-panel { max-height: 30dvh; }
|