@fasl-work/caos-app-shell 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,6 +87,14 @@ const config: ShellConfig = {
87
87
  `fitLabel`, `niceTicks`) and declares `data-chart` ([docs/05](docs/05_drawings.md)).
88
88
  - **Views in the URL:** `CaseWorkbench` and `SurfacePage` hold the open view in `?view=` (and the case selector the
89
89
  case in `?case=`), so a view can be shared and survives a reload.
90
+ - **Focus mode for the App (0.14.0):** `<CaseWorkbench focus />` adds a Focus button to the rail head that opens the
91
+ selected case's open group in the full viewport (`FocusShell`: the stage is the view, the rail stays), keeps `?focus=1`
92
+ beside `?case=` and `?view=`, and returns with Escape or the exit button to the same case, group and sub-view; a product with
93
+ a simulation must offer it (ADR-0070, `caos-shell-gate --require-focus`). See [docs/04](docs/04_views.md).
94
+ - **Small cards and animation (0.14.0):** `FlipCard` and `CardDeck` for SMALL cards the reader scans by title (a flip card
95
+ shows its title and turns to its content; a deck is a row up to four cards and a carousel above), used by the executive
96
+ view for its bearers, roles, approaches and 4 C's; an executive illustration may be an animated SVG with a
97
+ `prefers-reduced-motion` block ([docs/04](docs/04_views.md), [docs/06](docs/06_executive-view.md)).
90
98
  - **Icons:** lucide icons are sized by where they sit (`svg.lucide`); pass no `size` ([docs/06](docs/06_icons-and-numbers.md)).
91
99
  - **Numbers:** `formatNumber(value, lang, opts)` or `useFormat()`; never `toFixed` in a view. A magnitude below 1e-4
92
100
  is written in scientific notation (a p-value of 1e-200 is `1E-200`, not two hundred zeros); give `digits`, not
@@ -130,7 +138,7 @@ const config: ShellConfig = {
130
138
  `ChipGroup`, `Stage`, `useStageSize`, `makeStateKey`, `isStale`, `useWorkbenchState`,
131
139
  `WorkbenchStateContext`, `pick`; since 0.8.0 `SurfacePage`, `useUrlView`, `viewParamOf`, `replaceQueryParam`,
132
140
  `BREAKPOINTS`, `BarChart`, `textWidth`, `widestLabel`, `fitLabel`, `niceTicks`, `niceStep`, `fontFamily`,
133
- `formatTicks`, `NBSP`, `SCIENTIFIC_BELOW`, the type `ShellColorToken`; since 0.9.0 `ViewsRow`. Separate entries:
141
+ `formatTicks`, `NBSP`, `SCIENTIFIC_BELOW`, the type `ShellColorToken`; since 0.9.0 `ViewsRow`. Since 0.14.0 `FlipCard`, `CardDeck` (with `FLIP_LIMITS`, `FLIP_TEXT`, `DECK_TEXT`, `perPageFor`, `deckWindow`, `deckPosition`), the `focus` prop of `CaseWorkbench` (`CaseFocusConfig`, `FOCUS_TEXT`, `FOCUS_PARAM`) and `removeQueryParam`. Separate entries:
134
142
  `/chart` (`UPlotChart` with `x.log`, `parity`, `onPick`; `drawMarks`, `tickSteps`, `repeatedLabels`, `seriesStyle`,
135
143
  `sortOrder`, `parityRange`), `/keys` (the storage keys, no React), `/styles.css`, `/chart.css`,
136
144
  `/reserved-classes.json`. Bin: `caos-shell-gate`.
@@ -15,12 +15,13 @@
15
15
  // --single-case the workbench has one case and no case control
16
16
  // --idle-ms 3000 --settle-ms 15000 --text-floor 400 --instrument-min 0.5 --stage-fill-min 0.3 --stage-floor 140
17
17
  // --wide-font-sizes 390x844,1280x800 the sizes of the wide-font pass (G10); --no-wide-font skips it
18
+ // --require-focus fail an App without a Focus control (G18; ADR-0070 requires one in a product with simulations)
18
19
  // --out gate-output report (gate-report.json), screenshots (shots/) and the capture index (index.html)
19
20
  // Needs `playwright` in the consuming project (optional peer dependency). Exit 1 on any failure, 2 on misuse.
20
21
 
21
22
  import { runGate } from '../gate/run.mjs';
22
23
 
23
- const FLAGS = new Set(['single-case', 'no-wide-font']);
24
+ const FLAGS = new Set(['single-case', 'no-wide-font', 'require-focus']);
24
25
  const args = {};
25
26
  const argv = process.argv.slice(2);
26
27
  for (let i = 0; i < argv.length; i += 1) {
@@ -44,6 +45,7 @@ const options = {
44
45
  langs: args.langs,
45
46
  caseSample: args['case-sample'],
46
47
  singleCase: args['single-case'] === true,
48
+ requireFocus: args['require-focus'] === true,
47
49
  idleMs: num('idle-ms'),
48
50
  settleMs: num('settle-ms'),
49
51
  textFloor: num('text-floor'),
package/dist/index.d.ts CHANGED
@@ -63,9 +63,14 @@ interface FocusShellProps {
63
63
  onExit: () => void;
64
64
  exitLabel: string;
65
65
  stageLabel: string;
66
+ /**
67
+ * True (default): the frame is the page's `main`. False: the frame sits inside a page that already has its `main` (the App's
68
+ * Focus mode does), so it renders a `div` and the page holds one `main`.
69
+ */
70
+ landmark?: boolean;
66
71
  }
67
72
  /** Full-viewport, scenario-scoped analysis frame. The product supplies only its instrument and controls. */
68
- declare function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel }: FocusShellProps): react.JSX.Element;
73
+ declare function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel, landmark }: FocusShellProps): react.JSX.Element;
69
74
 
70
75
  /** Light/dark icon toggle. Shows the icon of the theme you would switch TO. */
71
76
  declare function ThemeToggle(): react.JSX.Element;
@@ -245,6 +250,9 @@ interface CaseSelectorProps {
245
250
  * category structure is preserved either way; only the height changes.
246
251
  */
247
252
  layout?: 'chips' | 'select';
253
+ /** A control that sits on the select's row, after it (the workbench's Focus button, shell 0.14.1): the select shrinks to make
254
+ * room, so the selector is no taller with it. With `layout: 'chips'` it gets a row of its own, after the chips. */
255
+ accessory?: ReactNode;
248
256
  }
249
257
  /**
250
258
  * CaseSelector v2, the shared source + case picker. Renders (1) an optional first-level source
@@ -665,6 +673,129 @@ interface QuestionProps {
665
673
  */
666
674
  declare function Question({ question, answer }: QuestionProps): react.JSX.Element;
667
675
 
676
+ /**
677
+ * The limits of a SMALL card (rule of use, CAOS_MANAGE plans/app-shell/0-14-0-plan.md decision 3): a title of about eight
678
+ * words and a back of about forty. They are counted in the interface language: Spanish runs about a quarter longer than
679
+ * English for the same text, so its limits are a quarter higher. `gate/areas.mjs` FLIP_LIMITS holds the same values and
680
+ * `test/flipCard.test.tsx` keeps the two equal.
681
+ */
682
+ declare const FLIP_LIMITS: {
683
+ readonly title: {
684
+ readonly en: 8;
685
+ readonly es: 10;
686
+ };
687
+ readonly back: {
688
+ readonly en: 40;
689
+ readonly es: 50;
690
+ };
691
+ };
692
+ /** The words the card itself writes (the product writes none of them). */
693
+ declare const FLIP_TEXT: {
694
+ readonly show: {
695
+ readonly en: "Show details";
696
+ readonly es: "Mostrar detalles";
697
+ };
698
+ readonly hide: {
699
+ readonly en: "Hide details";
700
+ readonly es: "Ocultar detalles";
701
+ };
702
+ };
703
+ interface FlipCardProps {
704
+ /** The face: a short title (a node, so a card may carry a source mark). */
705
+ title: ReactNode;
706
+ /** The title as plain text, repeated on the back beside the return control; omit it and the back shows none. */
707
+ label?: string;
708
+ /** The back: the content shown after the card turns. It stays in the markup while the face shows. */
709
+ children: ReactNode;
710
+ /** Base of the ids (default: generated). */
711
+ id?: string;
712
+ /** Start turned (default: face up). */
713
+ defaultOpen?: boolean;
714
+ /** Controlled turn, when the product holds it itself. */
715
+ open?: boolean;
716
+ onOpenChange?: (open: boolean) => void;
717
+ className?: string;
718
+ /** Forces the language of the card's own words (tests; the card otherwise follows the language toggle). */
719
+ lang?: Lang;
720
+ }
721
+ /**
722
+ * A small card whose face shows its title and whose back shows its content after a click or a key press (Enter or Space),
723
+ * and back again (shell 0.14.0). Use it only for a small card the reader scans by title first and opens when it matters
724
+ * (a title of about eight words, a back of about forty; the gate fails a card over the limit): a long card or an ordered
725
+ * sequence stays plain.
726
+ *
727
+ * - A real `button` carries `aria-expanded` and `aria-controls`; each face has one, so focus moves to the face that shows
728
+ * and never rests on a hidden one. Escape closes an open card.
729
+ * - The hidden face is out of the accessibility tree and the tab order (`aria-hidden` and `inert` at once, `visibility: hidden`
730
+ * after the turn) while its content stays in the markup (search, copy and the gate read it).
731
+ * - `prefers-reduced-motion: reduce` swaps the faces without the turn; colours are theme tokens only.
732
+ * - The face states that it turns: a small icon and a visually hidden label in both languages.
733
+ */
734
+ declare function FlipCard({ title, label, children, id, defaultOpen, open: controlled, onOpenChange, className, lang: forced }: FlipCardProps): react.JSX.Element;
735
+
736
+ /** The words the deck itself writes (the product writes none of them). */
737
+ declare const DECK_TEXT: {
738
+ readonly previous: {
739
+ readonly en: "Previous";
740
+ readonly es: "Anterior";
741
+ };
742
+ readonly next: {
743
+ readonly en: "Next";
744
+ readonly es: "Siguiente";
745
+ };
746
+ readonly of: {
747
+ readonly en: "of";
748
+ readonly es: "de";
749
+ };
750
+ readonly to: {
751
+ readonly en: "to";
752
+ readonly es: "a";
753
+ };
754
+ };
755
+ /** How many cards of at least `minCard` px fit a row of `width` px with `gap` px between them: at least one. */
756
+ declare function perPageFor(width: number, minCard: number, gap: number): number;
757
+ /** The window `[start, end)` of `n` cards that `per` at a time show from `start`: never empty, never past the end, pulled back so it is full. */
758
+ declare function deckWindow(n: number, per: number, start: number): {
759
+ start: number;
760
+ end: number;
761
+ };
762
+ /** "2 of 5" for one card, "1 to 3 of 5" for several (1-based). */
763
+ declare function deckPosition(start: number, shown: number, n: number, lang: Lang): string;
764
+ interface DeckCard {
765
+ /** Stable key (default: the index). */
766
+ id?: string | number;
767
+ /** The title of the card (a flip card's face). */
768
+ title: ReactNode;
769
+ /** The title as plain text, for the back of a flip card. */
770
+ label?: string;
771
+ /** The content: a plain card's body, a flip card's back. */
772
+ body: ReactNode;
773
+ /** Attributes written on the card's list item (for example `data-exec-item`). */
774
+ attrs?: Record<string, string | number | undefined>;
775
+ }
776
+ interface CardDeckProps {
777
+ cards: DeckCard[];
778
+ /** Render each card as a `FlipCard` (a small card the reader scans by title; see FlipCard for the rule of use). */
779
+ flip?: boolean;
780
+ /** A list longer than this is a carousel; up to it, a row (default 4). */
781
+ carouselAbove?: number;
782
+ /** The accessible name of the deck. */
783
+ ariaLabel: string;
784
+ /** Extra classes of the list that holds the cards. */
785
+ className?: string;
786
+ /** Forces the language of the deck's own words (tests). */
787
+ lang?: Lang;
788
+ }
789
+ /**
790
+ * A list of cards, plain or as flip cards, shown as a row up to `carouselAbove` cards (default 4) and as a CAROUSEL above
791
+ * it (shell 0.14.0): one row of as many cards as fit (at least one), previous and next buttons, a swipe on touch, the arrow
792
+ * keys, Home and End while it has focus, and a position ("2 of 5"). No autoplay. Every card is reachable by keyboard: the
793
+ * cards the window hides are removed from sight and from the tab order but stay in the markup. The carousel pages through
794
+ * its cards instead of scrolling a strip, so neither the page nor its panel ever scrolls sideways (ADR-0071). Reduced
795
+ * motion removes the slide. When every card fits, the controls are not drawn.
796
+ */
797
+ declare function CardDeck({ cards, flip, carouselAbove, ariaLabel, className, lang: forced }: CardDeckProps): react.JSX.Element;
798
+
668
799
  interface SurfacePageProps {
669
800
  /** The route's heading; omit it when the header's active route already names the surface. */
670
801
  title?: BiText;
@@ -697,6 +828,8 @@ declare function SurfacePage({ title, lede, actions, tabs, value, onChange, deep
697
828
  /** Writes one query parameter with replaceState, keeping the router's history state (React Router keeps its entry
698
829
  * key and index there; replacing it with null broke the index the back button relies on). */
699
830
  declare function replaceQueryParam(param: string, value: string): void;
831
+ /** Removes one query parameter with replaceState (the counterpart of `replaceQueryParam`). */
832
+ declare function removeQueryParam(param: string): void;
700
833
  /**
701
834
  * The open view of a tab row, held in the URL (`?view=` by default) so a reader can share it and a reload keeps it.
702
835
  * Returns the id to show and the selector to call. Controlled when `value` is given: the product holds the state and
@@ -966,6 +1099,44 @@ interface VariantBarProps {
966
1099
  * one-line note of what it shows. Selecting a regime must change every view of the case. */
967
1100
  declare function VariantBar({ variants, activeId, onSelect, title, lane }: VariantBarProps): react.JSX.Element;
968
1101
 
1102
+ /** The words Focus mode writes (the product writes none of them). */
1103
+ declare const FOCUS_TEXT: {
1104
+ readonly enter: {
1105
+ readonly en: "Focus";
1106
+ readonly es: "Enfocar";
1107
+ };
1108
+ readonly enterLabel: {
1109
+ readonly en: "Open this case in focus mode";
1110
+ readonly es: "Abrir este caso en modo enfoque";
1111
+ };
1112
+ readonly exit: {
1113
+ readonly en: "Back to the App";
1114
+ readonly es: "Volver a la App";
1115
+ };
1116
+ readonly stage: {
1117
+ readonly en: "Focus stage";
1118
+ readonly es: "Escenario de enfoque";
1119
+ };
1120
+ };
1121
+ /** The query parameter that holds Focus mode (`?focus=1`), beside `?case=` and `?view=`. */
1122
+ declare const FOCUS_PARAM = "focus";
1123
+ /** What a product may say about the Focus mode of its App (`CaseWorkbench.focus`; `true` means all defaults). */
1124
+ interface CaseFocusConfig {
1125
+ /** The stage's heading (default: the name of the selected case). */
1126
+ title?: BiText;
1127
+ /** One line under it (default: the name of the open group). */
1128
+ description?: BiText;
1129
+ /** Readouts laid over the stage (ADR-0070): at most four on a phone. */
1130
+ hud?: Array<{
1131
+ label: BiText;
1132
+ value: string;
1133
+ }>;
1134
+ /** Called when Focus opens (`true`) and closes (`false`), for a product that records it. */
1135
+ onChange?: (on: boolean) => void;
1136
+ /** The query parameter (default `focus`). */
1137
+ param?: string;
1138
+ }
1139
+
969
1140
  /** One group of views of the selected case, named for the question the user is asking (ADR-0071 rule 5). */
970
1141
  interface WorkbenchGroup {
971
1142
  id: string;
@@ -1016,6 +1187,13 @@ interface CaseWorkbenchProps {
1016
1187
  * keeps it out of the URL. Ignored when `group` is controlled. */
1017
1188
  deepLinkView?: boolean | string;
1018
1189
  ariaLabel?: BiText;
1190
+ /**
1191
+ * Focus mode for the App (ADR-0070, shell 0.14.0): a Focus button in the rail head opens the selected case's open group in
1192
+ * the full viewport through `FocusShell` (the stage is the view, the rail stays), holds `?focus=1` beside `?case=` and
1193
+ * `?view=` in the URL, and returns with Escape or the exit button to the same case, group and sub-view. `true` takes all
1194
+ * the defaults. Omit it and the App is exactly what it was.
1195
+ */
1196
+ focus?: boolean | CaseFocusConfig;
1019
1197
  }
1020
1198
  declare const MAX_WORKBENCH_GROUPS = 6;
1021
1199
  /**
@@ -1104,4 +1282,4 @@ interface ViewsRowProps {
1104
1282
  */
1105
1283
  declare function ViewsRow({ children, shares }: ViewsRowProps): react.JSX.Element;
1106
1284
 
1107
- export { BREAKPOINTS, BarChart, type BarChartProps, type BarDatum, BiText, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, CaseWorkbench, type CaseWorkbenchProps, ChipGroup, type ChipGroupProps, type ChipOption, type Citation, CitationsProvider, Cite, DOC_AREAS, DocPage, type DocPageProps, DocReferences, DocRefsScope, DocSection, type DocSectionProps, EXECUTIVE_SCHEMA_VERSION, Equation, type ExecutiveData, type ExecutiveProblem, ExecutiveView, type ExecutiveViewProps, FOUR_CS, Figure, type FittedLabel, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, Glossary, type GlossaryEntry, InlineMath, Knob, type KnobProps, Landing, type Lane, LaneBadge, Lang, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, Question, type QuestionProps, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Stage, type StageSize, Stat, type StatProps, type SubTabDef, SubTabs, type SubTabsProps, 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, Verdict, type VerdictProps, 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, caseKind, caseTooltip, casesInSource, createVizLoop, firstAppearance, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, readCaseParam, readTheme, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useStageSize, useThemeStore, useUrlView, useWorkbenchState, validateExecutive, viewParamOf, widestLabel, withCaseParam };
1285
+ export { BREAKPOINTS, BarChart, type BarChartProps, type BarDatum, BiText, Callout, 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, DocPage, type DocPageProps, DocReferences, DocRefsScope, DocSection, type DocSectionProps, EXECUTIVE_SCHEMA_VERSION, Equation, type ExecutiveData, type ExecutiveProblem, ExecutiveView, type ExecutiveViewProps, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, type FittedLabel, FlipCard, type FlipCardProps, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, Glossary, type GlossaryEntry, InlineMath, Knob, type KnobProps, Landing, type Lane, LaneBadge, Lang, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, Question, type QuestionProps, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Stage, type StageSize, Stat, type StatProps, type SubTabDef, SubTabs, type SubTabsProps, 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, Verdict, type VerdictProps, 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, caseKind, caseTooltip, casesInSource, createVizLoop, deckPosition, deckWindow, firstAppearance, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, perPageFor, readCaseParam, readTheme, removeQueryParam, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useStageSize, useThemeStore, useUrlView, useWorkbenchState, validateExecutive, viewParamOf, widestLabel, withCaseParam };