@fasl-work/caos-app-shell 0.6.8 → 0.6.13
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 +1 -1
- package/dist/index.d.ts +25 -9
- package/dist/index.js +24 -3
- package/package.json +2 -1
- package/styles.css +49 -6
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ const config: ShellConfig = {
|
|
|
57
57
|
(labelled groups, `Synthetic | Real | Uploaded` source control, locked-knobs explanation, divergence
|
|
58
58
|
badge, `?case=` deep-linking).
|
|
59
59
|
- **Animated views:** drive every canvas/3D loop through `usePausedViz` (default paused, run-once,
|
|
60
|
-
halt on a hidden tab)
|
|
60
|
+
halt on a hidden tab), never call `requestAnimationFrame` directly.
|
|
61
61
|
|
|
62
62
|
## Exports
|
|
63
63
|
|
package/dist/index.d.ts
CHANGED
|
@@ -131,6 +131,22 @@ interface WorkbenchShellProps {
|
|
|
131
131
|
/** Shared CAOS frame for authenticated consoles with desktop sidebar and mobile bottom navigation. */
|
|
132
132
|
declare function WorkbenchShell({ brand, routes, navigationLabel, children, sidebarFooter, headerLead, headerActions, overlays, mainId, className, }: WorkbenchShellProps): react.JSX.Element;
|
|
133
133
|
|
|
134
|
+
interface FocusShellProps {
|
|
135
|
+
stage: ReactNode;
|
|
136
|
+
rail: ReactNode;
|
|
137
|
+
title: string;
|
|
138
|
+
description: string;
|
|
139
|
+
hud: Array<{
|
|
140
|
+
label: string;
|
|
141
|
+
value: string;
|
|
142
|
+
}>;
|
|
143
|
+
onExit: () => void;
|
|
144
|
+
exitLabel: string;
|
|
145
|
+
stageLabel: string;
|
|
146
|
+
}
|
|
147
|
+
/** Full-viewport, scenario-scoped analysis frame. The product supplies only its instrument and controls. */
|
|
148
|
+
declare function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel }: FocusShellProps): react.JSX.Element;
|
|
149
|
+
|
|
134
150
|
/** Light/dark icon toggle. Shows the icon of the theme you would switch TO. */
|
|
135
151
|
declare function ThemeToggle(): react.JSX.Element;
|
|
136
152
|
|
|
@@ -167,13 +183,13 @@ interface VizLoopOptions {
|
|
|
167
183
|
/** Auto-complete the pass once `elapsed` reaches this many ms (a hard cap so a frame that never
|
|
168
184
|
* returns false still stops). The final frame is drawn clamped to exactly this value. */
|
|
169
185
|
durationMs?: number;
|
|
170
|
-
/** Fired once each time the loop stops on its own (a completed pass with loop=false)
|
|
186
|
+
/** Fired once each time the loop stops on its own (a completed pass with loop=false), not on a
|
|
171
187
|
* manual pause and not on a visibility halt. */
|
|
172
188
|
onComplete?: () => void;
|
|
173
189
|
/** Notified whenever the running state changes, so a React wrapper can mirror it into state. */
|
|
174
190
|
onPlayingChange?: (playing: boolean) => void;
|
|
175
191
|
}
|
|
176
|
-
/** Injected side effects
|
|
192
|
+
/** Injected side effects, real ones in the hook, fakes in tests. The frame timestamp comes from
|
|
177
193
|
* the rAF callback argument, so no separate clock is needed. */
|
|
178
194
|
interface VizLoopDeps {
|
|
179
195
|
raf: (cb: (now: number) => void) => number;
|
|
@@ -206,7 +222,7 @@ interface PausedVizController {
|
|
|
206
222
|
restart: () => void;
|
|
207
223
|
}
|
|
208
224
|
interface UsePausedVizOptions extends VizLoopOptions {
|
|
209
|
-
/** Begin playing on mount. Default false
|
|
225
|
+
/** Begin playing on mount. Default false, the mandatory no-autoplay default: a page must never
|
|
210
226
|
* burn CPU unattended. Opt in only for a view the user explicitly expects to move on arrival. */
|
|
211
227
|
autoStart?: boolean;
|
|
212
228
|
}
|
|
@@ -224,7 +240,7 @@ declare function usePausedViz(frame: VizFrame, options?: UsePausedVizOptions): P
|
|
|
224
240
|
|
|
225
241
|
type CaseKind = 'synthetic' | 'real' | 'uploaded';
|
|
226
242
|
interface CaseDef {
|
|
227
|
-
/** stable slug
|
|
243
|
+
/** stable slug, the value used in the ?case= deep link and reported to onSelect. */
|
|
228
244
|
id: string;
|
|
229
245
|
/** short human name shown ON the chip next to the id (never demoted to a tooltip-only string). */
|
|
230
246
|
name: string;
|
|
@@ -232,9 +248,9 @@ interface CaseDef {
|
|
|
232
248
|
category?: string;
|
|
233
249
|
/** which source lane this case belongs to. Defaults to 'synthetic'. */
|
|
234
250
|
kind?: CaseKind;
|
|
235
|
-
/** validation anchor (e.g. "published optimum 26,086,899")
|
|
251
|
+
/** validation anchor (e.g. "published optimum 26,086,899"), surfaced in the chip tooltip. */
|
|
236
252
|
anchor?: string;
|
|
237
|
-
/** expected-range hint
|
|
253
|
+
/** expected-range hint, surfaced in the chip tooltip. */
|
|
238
254
|
expectedBand?: string;
|
|
239
255
|
disabled?: boolean;
|
|
240
256
|
}
|
|
@@ -272,7 +288,7 @@ interface CaseSelectorProps {
|
|
|
272
288
|
selectedId: string;
|
|
273
289
|
onSelect: (id: string) => void;
|
|
274
290
|
/** When provided, renders the first-level source control and filters cases to the active lane.
|
|
275
|
-
* Controlled
|
|
291
|
+
* Controlled, pair with onSourceChange. Omit for a single-lane deck (no source control). */
|
|
276
292
|
source?: CaseKind;
|
|
277
293
|
onSourceChange?: (k: CaseKind) => void;
|
|
278
294
|
/** Overrides the default locked-knobs explanation shown for a non-synthetic source. */
|
|
@@ -301,7 +317,7 @@ interface CaseSelectorProps {
|
|
|
301
317
|
layout?: 'chips' | 'select';
|
|
302
318
|
}
|
|
303
319
|
/**
|
|
304
|
-
* CaseSelector v2
|
|
320
|
+
* CaseSelector v2, the shared source + case picker. Renders (1) an optional first-level source
|
|
305
321
|
* segmented control (Synthetic | Real | Uploaded, only the lanes present), (2) a labelled group per
|
|
306
322
|
* case category, each case a chip showing "ID · name" with its validation anchor as the tooltip and
|
|
307
323
|
* a source tag, (3) a locked-knobs explanation when a non-synthetic source is active, and (4) a
|
|
@@ -391,4 +407,4 @@ declare function ReferenceList({ ids, heading }: {
|
|
|
391
407
|
heading?: string;
|
|
392
408
|
}): react.JSX.Element;
|
|
393
409
|
|
|
394
|
-
export { AppShell, type ArchTab, type ArchitectureConfig, ArchitectureModal, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, type Citation, CitationsProvider, Cite, Equation, Figure, InlineMath, type Lang, LanguageToggle, type PausedVizController, ReferenceList, Refs, type ShellConfig, type ShellRoute, type SubTabDef, SubTabs, type TabDef, Tabs, type Theme, ThemeToggle, type UsePausedVizOptions, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
|
410
|
+
export { AppShell, type ArchTab, type ArchitectureConfig, ArchitectureModal, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, type Citation, CitationsProvider, Cite, Equation, Figure, FocusShell, type FocusShellProps, InlineMath, type Lang, LanguageToggle, type PausedVizController, ReferenceList, Refs, type ShellConfig, type ShellRoute, type SubTabDef, SubTabs, type TabDef, Tabs, type Theme, ThemeToggle, type UsePausedVizOptions, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
package/dist/index.js
CHANGED
|
@@ -360,7 +360,7 @@ function AppShell({ config, children }) {
|
|
|
360
360
|
/* @__PURE__ */ jsx("span", { children: config.footer.provenance[lang] })
|
|
361
361
|
] }),
|
|
362
362
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
363
|
-
/* @__PURE__ */ jsx("a", { href: config.links.github, target: "_blank", rel: "noreferrer noopener", children: c.github }),
|
|
363
|
+
/* @__PURE__ */ jsx("a", { href: config.links.github, target: "_blank", rel: "noreferrer noopener", onFocus: (event) => event.currentTarget.scrollIntoView({ block: "nearest", inline: "nearest" }), children: c.github }),
|
|
364
364
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
365
365
|
/* @__PURE__ */ jsx("span", { className: "faint", children: config.footer?.license?.[lang] ?? c.license }),
|
|
366
366
|
config.footer?.disclaimer && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -419,6 +419,27 @@ function WorkbenchShell({
|
|
|
419
419
|
overlays
|
|
420
420
|
] });
|
|
421
421
|
}
|
|
422
|
+
function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel }) {
|
|
423
|
+
return /* @__PURE__ */ jsxs("main", { className: "caos-focus-shell", children: [
|
|
424
|
+
/* @__PURE__ */ jsxs("section", { className: "caos-focus-stage", "aria-label": stageLabel, children: [
|
|
425
|
+
/* @__PURE__ */ jsx("div", { className: "caos-focus-instrument", children: stage }),
|
|
426
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-focus-label", children: [
|
|
427
|
+
/* @__PURE__ */ jsx("strong", { children: title }),
|
|
428
|
+
/* @__PURE__ */ jsx("span", { children: description })
|
|
429
|
+
] }),
|
|
430
|
+
/* @__PURE__ */ jsx("div", { className: "caos-focus-hud", "aria-label": "HUD", children: hud.map((item) => /* @__PURE__ */ jsxs("div", { children: [
|
|
431
|
+
/* @__PURE__ */ jsx("strong", { children: item.value }),
|
|
432
|
+
/* @__PURE__ */ jsx("span", { children: item.label })
|
|
433
|
+
] }, item.label)) }),
|
|
434
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-focus-actions", children: [
|
|
435
|
+
/* @__PURE__ */ jsx(LanguageToggle, {}),
|
|
436
|
+
/* @__PURE__ */ jsx(ThemeToggle, {}),
|
|
437
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onExit, children: exitLabel })
|
|
438
|
+
] })
|
|
439
|
+
] }),
|
|
440
|
+
/* @__PURE__ */ jsx("aside", { className: "caos-focus-rail", children: rail })
|
|
441
|
+
] });
|
|
442
|
+
}
|
|
422
443
|
|
|
423
444
|
// src/lib/vizLoop.ts
|
|
424
445
|
function createVizLoop(frame, options, deps) {
|
|
@@ -601,7 +622,7 @@ var TEXT = {
|
|
|
601
622
|
synthetic: "Synthetic",
|
|
602
623
|
real: "Real",
|
|
603
624
|
uploaded: "Uploaded",
|
|
604
|
-
lockedNote: "Scenario knobs are locked on a real sample
|
|
625
|
+
lockedNote: "Scenario knobs are locked on a real sample: you pick which datum; every tool runs on it as-is.",
|
|
605
626
|
modifiedPrefix: "modified from",
|
|
606
627
|
reset: "reset"
|
|
607
628
|
},
|
|
@@ -910,4 +931,4 @@ function ReferenceList({ ids, heading }) {
|
|
|
910
931
|
] });
|
|
911
932
|
}
|
|
912
933
|
|
|
913
|
-
export { AppShell, ArchitectureModal, Callout, CaseSelector, CitationsProvider, Cite, Equation, Figure, InlineMath, LanguageToggle, ReferenceList, Refs, SubTabs, Tabs, ThemeToggle, WorkbenchShell, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
|
934
|
+
export { AppShell, ArchitectureModal, Callout, CaseSelector, CitationsProvider, Cite, Equation, Figure, FocusShell, InlineMath, LanguageToggle, ReferenceList, Refs, SubTabs, Tabs, ThemeToggle, WorkbenchShell, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
4
4
|
"description": "Shared web-app shell + content primitives + design system for the CAOS / Faena public apps (ADR-0016).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Felipe Santibáñez-Leal",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"build": "tsup",
|
|
31
31
|
"test": "node --import tsx --test \"test/**/*.test.ts\" \"test/**/*.test.tsx\"",
|
|
32
32
|
"test:browser": "node --import tsx --test \"e2e/**/*.test.ts\"",
|
|
33
|
+
"prepare": "npm run build",
|
|
33
34
|
"prepublishOnly": "npm run build"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
package/styles.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@import "katex/dist/katex.min.css";
|
|
2
2
|
|
|
3
|
-
/* @fasl-work/caos-app-shell
|
|
3
|
+
/* @fasl-work/caos-app-shell, shared design system (ADR-0016 + visual-standards palette).
|
|
4
4
|
* Theming via CSS custom properties keyed on data-theme on <html> (set pre-paint by an inline
|
|
5
5
|
* script in index.html, kept in sync by the shell theme store). Import once per app:
|
|
6
6
|
* import "@fasl-work/caos-app-shell/styles.css"; */
|
|
@@ -111,11 +111,20 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
111
111
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
|
|
112
112
|
:where(a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])):focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 4px; }
|
|
113
113
|
@media (max-width: 760px) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.header-
|
|
117
|
-
.
|
|
118
|
-
.
|
|
114
|
+
/* ADR-0071: route navigation shares the header row; it owns the horizontal
|
|
115
|
+
scroll instead of adding a permanent second row above every instrument. */
|
|
116
|
+
.header-inner { height: 52px; min-height: 0; flex-wrap: nowrap; gap: 0.3rem; padding-inline: 0.55rem; }
|
|
117
|
+
.brand, .header-actions { flex: none; white-space: nowrap; }
|
|
118
|
+
.brand { gap: 0.25rem; font-size: 0.8rem; }
|
|
119
|
+
.header-actions { margin-left: 0; gap: 0; }
|
|
120
|
+
.header-actions .icon-btn { padding: 0.25rem; }
|
|
121
|
+
.header-sep { margin-inline: 0.15rem; }
|
|
122
|
+
.main-nav { flex: 1 1 0; min-width: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; padding: 2px 0; scrollbar-width: none; }
|
|
123
|
+
.main-nav::-webkit-scrollbar { display: none; }
|
|
124
|
+
.main-nav .nav-link { flex: none; padding: 0.3rem 0.3rem; font-size: 0.7rem; }
|
|
125
|
+
}
|
|
126
|
+
@media (max-width: 380px) {
|
|
127
|
+
.brand span:last-child { display: none; }
|
|
119
128
|
}
|
|
120
129
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
|
|
121
130
|
|
|
@@ -325,6 +334,14 @@ html, body { overflow-x: hidden; }
|
|
|
325
334
|
text stays (ADR-0016 s2: provenance and the honest disclaimer are the footer's job); it is set
|
|
326
335
|
compact, so it takes two to three lines, not five. */
|
|
327
336
|
.app-shell.fixed .site-footer .footer-inner { padding: 0.45rem 1.5rem; gap: 0.4rem; font-size: 0.74rem; line-height: 1.3; }
|
|
337
|
+
@media (max-width: 760px) {
|
|
338
|
+
/* A long, correctly attributed provenance must not consume the instrument.
|
|
339
|
+
Footer metadata remains accessible by horizontal scroll and keyboard focus. */
|
|
340
|
+
.app-shell.fixed .site-footer .footer-inner { padding: 0.35rem 0.55rem; }
|
|
341
|
+
.app-shell.fixed .footer-meta { flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
|
|
342
|
+
.app-shell.fixed .footer-meta::-webkit-scrollbar { display: none; }
|
|
343
|
+
.app-shell.fixed .footer-meta > * { flex: none; }
|
|
344
|
+
}
|
|
328
345
|
|
|
329
346
|
/* ADR-0058 diagram i18n. A hand-authored architecture SVG carries BOTH languages inside ONE file,
|
|
330
347
|
and the modal wrapper picks one. Two files would be two things to keep in step, and the one not
|
|
@@ -338,3 +355,29 @@ html, body { overflow-x: hidden; }
|
|
|
338
355
|
/* The compact one-of-N case picker (ADR-0071 rule 7). Ten cases in four categories
|
|
339
356
|
cost a 308px chip block, 21% of a 2560x1440 viewport, and one row as a dropdown. */
|
|
340
357
|
.cs-select { width: 100%; max-width: 460px; }
|
|
358
|
+
|
|
359
|
+
/* ADR-0070: additive, shell-owned, full-viewport scenario focus frame. */
|
|
360
|
+
.caos-focus-shell { position: fixed; inset: 0; z-index: 100; display: grid; grid-template-columns: minmax(0,1fr) clamp(300px,20vw,340px); min-width: 0; height: 100dvh; background: var(--color-bg); color: var(--color-fg); }
|
|
361
|
+
.caos-focus-stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: var(--color-bg); }
|
|
362
|
+
.caos-focus-instrument { position: absolute; inset: 0; min-width: 0; min-height: 0; }
|
|
363
|
+
.caos-focus-label, .caos-focus-hud > div { background: color-mix(in oklab,var(--color-surface) 82%,transparent); backdrop-filter: blur(6px); border: 1px solid var(--color-border); border-radius: 6px; }
|
|
364
|
+
.caos-focus-label { position: absolute; top: 14px; left: 14px; max-width: min(46ch,calc(100% - 230px)); display: grid; gap: 4px; padding: 10px 12px; pointer-events: none; }
|
|
365
|
+
.caos-focus-label strong { font-size: 0.96rem; line-height: 1.2; }
|
|
366
|
+
.caos-focus-label span { font-size: 0.78rem; line-height: 1.4; color: var(--color-fg-subtle); }
|
|
367
|
+
.caos-focus-hud { position: absolute; left: 14px; top: 104px; display: grid; gap: 6px; pointer-events: none; }
|
|
368
|
+
.caos-focus-hud > div { display: grid; gap: 1px; padding: 7px 10px; min-width: 120px; }
|
|
369
|
+
.caos-focus-hud strong { font: 700 1rem var(--font-mono,monospace); }
|
|
370
|
+
.caos-focus-hud span { color: var(--color-fg-subtle); font-size: 0.61rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; }
|
|
371
|
+
.caos-focus-actions { position: absolute; top: 12px; right: 12px; display: flex; gap: 6px; align-items: center; padding: 4px; border: 1px solid var(--color-border); border-radius: 6px; background: color-mix(in oklab,var(--color-surface) 90%,transparent); backdrop-filter: blur(6px); }
|
|
372
|
+
.caos-focus-actions button { border: 0; border-radius: 4px; padding: 6px 9px; background: var(--color-surface-2); color: var(--color-fg); font-size: 0.75rem; font-weight: 700; cursor: pointer; }
|
|
373
|
+
.caos-focus-rail { min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; border-left: 1px solid var(--color-border); background: var(--color-surface); }
|
|
374
|
+
@media (max-width: 860px) {
|
|
375
|
+
.caos-focus-shell { grid-template-columns: minmax(0,1fr); grid-template-rows: minmax(0,54dvh) minmax(0,46dvh); }
|
|
376
|
+
.caos-focus-rail { border-left: 0; border-top: 1px solid var(--color-border); }
|
|
377
|
+
.caos-focus-label { max-width: calc(100% - 16px); top: 49px; left: 8px; padding: 6px 8px; }
|
|
378
|
+
.caos-focus-hud { top: auto; bottom: 8px; left: 8px; right: 8px; display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 4px; }
|
|
379
|
+
.caos-focus-hud > div { min-width: 0; padding: 5px; }
|
|
380
|
+
.caos-focus-hud strong { font-size: 0.72rem; white-space: nowrap; }
|
|
381
|
+
.caos-focus-hud span { font-size: 0.48rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
382
|
+
.caos-focus-actions { top: 7px; right: 7px; }
|
|
383
|
+
}
|