@fasl-work/caos-app-shell 0.20.1 → 0.22.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 +42 -0
- package/VERSION +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +16 -11
- package/dist/{keys-DDQGijwQ.d.ts → keys-D-pSo8hG.d.ts} +2 -0
- package/dist/keys.d.ts +1 -1
- package/package.json +1 -1
- package/reserved-classes.json +4 -0
- package/styles.css +34 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## [0.22.000] - 2026-11-06
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- Add explicit `density="compact"` to `Tabs` and `tabDensity="compact"` to `CaseWorkbench`, preserving labels, keyboard behavior and overflow while reducing vertical navigation chrome for short viewports.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Keep six guided story steps on one horizontally scrollable row at phone, tablet and desktop widths (SHW-055, issue 183).
|
|
10
|
+
|
|
11
|
+
### Adoption
|
|
12
|
+
|
|
13
|
+
- Products with short viewports may opt into compact tab rows; default density remains unchanged.
|
|
14
|
+
|
|
15
|
+
## [0.21.000] - 2026-11-05
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Add `ShellConfig.immersiveRoutes` for route-specific full-viewport story framing. The route keeps the skip link and product identity on the main landmark while the shell omits header and footer; phone layouts return to document scrolling.
|
|
20
|
+
- Add the bounded `railWidth` API to `WorkbenchLayout` and `CaseWorkbench`, with shared compact rail spacing for measured workbench layouts.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Make a filling `PlotCard` containing a `Stage` and a direct child marked `data-caos-plot-control` allocate drawing height to the stage while keeping the control reachable, with no product CSS override.
|
|
25
|
+
|
|
26
|
+
### Adoption
|
|
27
|
+
|
|
28
|
+
- Products use `immersiveRoutes` and `railWidth` for guided full-screen stories and tuned desktop rails, then remove CSS overrides of shell-reserved classes.
|
|
1
29
|
## [0.20.001] - 2026-11-05
|
|
2
30
|
|
|
3
31
|
### Fixed
|
|
@@ -1301,3 +1329,17 @@ All notable changes to this product. Format: `X.XX.XXX` (display, see the worksp
|
|
|
1301
1329
|
|
|
1302
1330
|
- 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.
|
|
1303
1331
|
- 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.
|
|
1332
|
+
## [0.21.000] - 2026-11-05
|
|
1333
|
+
|
|
1334
|
+
### Added
|
|
1335
|
+
|
|
1336
|
+
- Add `ShellConfig.immersiveRoutes` for route-specific full-viewport story framing. The route keeps the skip link and product identity on the main landmark while the shell omits header and footer; phone layouts return to document scrolling.
|
|
1337
|
+
- Add the bounded `railWidth` API to `WorkbenchLayout` and `CaseWorkbench`, with shared compact rail spacing for measured workbench layouts.
|
|
1338
|
+
|
|
1339
|
+
### Fixed
|
|
1340
|
+
|
|
1341
|
+
- Make a filling `PlotCard` containing a `Stage` and a direct child marked `data-caos-plot-control` allocate drawing height to the stage while keeping the control reachable, with no product CSS override.
|
|
1342
|
+
|
|
1343
|
+
### Adoption
|
|
1344
|
+
|
|
1345
|
+
- Products use `immersiveRoutes` and `railWidth` for guided full-screen stories and tuned desktop rails, then remove CSS overrides of shell-reserved classes.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.22.000
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as APP_ROUTE, a as AppShell, b as ArchTab, c as ArchitectureConfig, d as ArchitectureModal, L as LANDING_ROUTE, e as LANG_STORAGE_KEY, S as STANDARD_ROUTES, f as ShellConfig, g as ShellRoute, T as THEME_BOOT_SCRIPT, h as THEME_STORAGE_KEY, v as validateArchitectureConfig } from './keys-
|
|
1
|
+
export { A as APP_ROUTE, a as AppShell, b as ArchTab, c as ArchitectureConfig, d as ArchitectureModal, L as LANDING_ROUTE, e as LANG_STORAGE_KEY, S as STANDARD_ROUTES, f as ShellConfig, g as ShellRoute, T as THEME_BOOT_SCRIPT, h as THEME_STORAGE_KEY, v as validateArchitectureConfig } from './keys-D-pSo8hG.js';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, Component, ErrorInfo, ChangeEvent } from 'react';
|
|
4
4
|
import * as zustand from 'zustand';
|
|
@@ -361,6 +361,8 @@ interface TabDef {
|
|
|
361
361
|
declare const MAX_PEER_TABS = 6;
|
|
362
362
|
interface TabsProps {
|
|
363
363
|
tabs: TabDef[];
|
|
364
|
+
/** Explicit row density for short workbench viewports; `comfortable` preserves the default spacing. */
|
|
365
|
+
density?: 'comfortable' | 'compact';
|
|
364
366
|
/** Uncontrolled: the first selected tab. */
|
|
365
367
|
initial?: string;
|
|
366
368
|
/** Controlled: the selected tab, owned by the app (for example a view held in the URL). */
|
|
@@ -380,7 +382,7 @@ interface TabsProps {
|
|
|
380
382
|
* is rendered, inside an error boundary; a row wider than its box fades the hidden end. Controlled when `value` is
|
|
381
383
|
* given (known shell defect 6). More than six peers is reported (rule 5).
|
|
382
384
|
*/
|
|
383
|
-
declare function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing }: TabsProps): react.JSX.Element;
|
|
385
|
+
declare function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing, density }: TabsProps): react.JSX.Element;
|
|
384
386
|
|
|
385
387
|
interface SubTabDef {
|
|
386
388
|
id: string;
|
|
@@ -1169,6 +1171,8 @@ interface WorkbenchLayoutProps {
|
|
|
1169
1171
|
/** Accessible name of the rail. */
|
|
1170
1172
|
railLabel?: BiText;
|
|
1171
1173
|
className?: string;
|
|
1174
|
+
/** Product-calibrated desktop rail width in CSS pixels. Keep within 150–360 px. */
|
|
1175
|
+
railWidth?: number;
|
|
1172
1176
|
}
|
|
1173
1177
|
/**
|
|
1174
1178
|
* The App route of a CAOS product: a sized control rail beside the instrument, on the full viewport.
|
|
@@ -1178,7 +1182,7 @@ interface WorkbenchLayoutProps {
|
|
|
1178
1182
|
* explicit width that never scrolls (rule 6), and an instrument marked `data-instrument` so the measured gate
|
|
1179
1183
|
* can check that it covers at least 60% of the viewport, and Focus at least 80% (ADR-0082). Pair with `ShellConfig.contain`.
|
|
1180
1184
|
*/
|
|
1181
|
-
declare function WorkbenchLayout({ rail, railHead, children, railLabel, className }: WorkbenchLayoutProps): react.JSX.Element;
|
|
1185
|
+
declare function WorkbenchLayout({ rail, railHead, children, railLabel, className, railWidth }: WorkbenchLayoutProps): react.JSX.Element;
|
|
1182
1186
|
|
|
1183
1187
|
/** One configured regime of a case: a full parameter vector, never a cosmetic label (ADR-0016 §9). */
|
|
1184
1188
|
interface VariantDef {
|
|
@@ -1232,6 +1236,10 @@ interface CaseWorkbenchProps {
|
|
|
1232
1236
|
* as one node or as sections shown one at a time (ADR-0071 rule 6). */
|
|
1233
1237
|
rail?: ReactNode | RailSection[];
|
|
1234
1238
|
railLabel?: BiText;
|
|
1239
|
+
/** Desktop control-rail width, passed to WorkbenchLayout (150–360 px). */
|
|
1240
|
+
railWidth?: number;
|
|
1241
|
+
/** Density of the case-group tab row; compact preserves every tab while reducing vertical chrome. */
|
|
1242
|
+
tabDensity?: 'comfortable' | 'compact';
|
|
1235
1243
|
/** The instrument: the case's own question groups (for example Model, Validation, Impact, Findings). */
|
|
1236
1244
|
groups: WorkbenchGroup[];
|
|
1237
1245
|
/** The comparison across the case's variants (one metric per regime; selecting one loads it). */
|
package/dist/index.js
CHANGED
|
@@ -377,14 +377,15 @@ function Frame({ config, pathname, routed, children }) {
|
|
|
377
377
|
}, [lang]);
|
|
378
378
|
const archLabel = lang === "es" ? "Arquitectura / C\xF3mo funciona" : "Architecture / How it works";
|
|
379
379
|
const fixed = config.contain === true || config.fixed === true || (config.fixedRoutes ?? []).some((p) => p === pathname || p !== "/" && pathname.startsWith(p.replace(/\/$/, "") + "/"));
|
|
380
|
+
const immersive = (config.immersiveRoutes ?? []).some((p) => p === pathname || p !== "/" && pathname.startsWith(p.replace(/\/$/, "") + "/"));
|
|
380
381
|
const license = config.license ? pick(config.license, lang) : config.footer?.license?.[lang] ?? "";
|
|
381
382
|
const brand = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
382
383
|
/* @__PURE__ */ jsx("span", { className: "brand-mark", children: config.product.mark ?? /* @__PURE__ */ jsx(Boxes, { size: 18, "aria-hidden": "true" }) }),
|
|
383
384
|
/* @__PURE__ */ jsx("span", { children: config.product.name })
|
|
384
385
|
] });
|
|
385
|
-
return /* @__PURE__ */ jsxs("div", { className: config.contain ? "
|
|
386
|
+
return /* @__PURE__ */ jsxs("div", { className: ["app-shell", config.contain || fixed || immersive ? "fixed" : "", config.contain ? "contain" : "", immersive ? "immersive" : ""].filter(Boolean).join(" "), children: [
|
|
386
387
|
/* @__PURE__ */ jsx("a", { className: "skip-link", href: "#main", children: c.skip }),
|
|
387
|
-
/* @__PURE__ */ jsx("header", { className: "site-header", children: /* @__PURE__ */ jsxs("div", { className: "header-inner", children: [
|
|
388
|
+
!immersive && /* @__PURE__ */ jsx("header", { className: "site-header", children: /* @__PURE__ */ jsxs("div", { className: "header-inner", children: [
|
|
388
389
|
routed ? /* @__PURE__ */ jsx(NavLink, { to: "/", className: "brand", "aria-label": config.product.name, "data-brand": config.product.name, children: brand }) : /* @__PURE__ */ jsx("a", { href: "/", className: "brand", "aria-label": config.product.name, "data-brand": config.product.name, children: brand }),
|
|
389
390
|
routes.length > 1 && /* @__PURE__ */ jsx(Nav, { routes, name: config.product.name, routed, pathname }),
|
|
390
391
|
/* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
|
|
@@ -398,8 +399,8 @@ function Frame({ config, pathname, routed, children }) {
|
|
|
398
399
|
] })
|
|
399
400
|
] }) }),
|
|
400
401
|
config.architecture && archOpen && /* @__PURE__ */ jsx(ArchitectureModal, { config: config.architecture, onClose: () => setArchOpen(false) }),
|
|
401
|
-
/* @__PURE__ */ jsx("main", { className: "page", id: "main", tabIndex: -1, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: "page", children }) }),
|
|
402
|
-
/* @__PURE__ */ jsx("footer", { className: "site-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-inner", children: /* @__PURE__ */ jsxs("div", { className: "footer-meta", ref: footRef, children: [
|
|
402
|
+
/* @__PURE__ */ jsx("main", { className: "page", id: "main", tabIndex: -1, "data-brand": config.product.name, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: "page", children }) }),
|
|
403
|
+
!immersive && /* @__PURE__ */ jsx("footer", { className: "site-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-inner", children: /* @__PURE__ */ jsxs("div", { className: "footer-meta", ref: footRef, children: [
|
|
403
404
|
/* @__PURE__ */ jsx("span", { children: config.product.name }),
|
|
404
405
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
405
406
|
/* @__PURE__ */ jsx("span", { children: c.complement }),
|
|
@@ -1049,7 +1050,7 @@ function ReferenceList({ ids, heading }) {
|
|
|
1049
1050
|
] });
|
|
1050
1051
|
}
|
|
1051
1052
|
var MAX_PEER_TABS = 6;
|
|
1052
|
-
function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing }) {
|
|
1053
|
+
function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing, density = "comfortable" }) {
|
|
1053
1054
|
const baseId = useId();
|
|
1054
1055
|
const first = tabs[0]?.id ?? "";
|
|
1055
1056
|
const memory = useContext(ViewMemoryContext);
|
|
@@ -1085,7 +1086,7 @@ function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing }) {
|
|
|
1085
1086
|
}
|
|
1086
1087
|
}
|
|
1087
1088
|
const current = tabs.find((t2) => t2.id === active) ?? tabs[0];
|
|
1088
|
-
return /* @__PURE__ */ jsxs("div", { className: trailing === void 0 ? "tabs" : "tabs tabs-has-slot"
|
|
1089
|
+
return /* @__PURE__ */ jsxs("div", { className: `${trailing === void 0 ? "tabs" : "tabs tabs-has-slot"}${density === "compact" ? " tabs-compact" : ""}`, children: [
|
|
1089
1090
|
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, ref: rowRef, children: tabs.map((tab, idx) => {
|
|
1090
1091
|
const selected = tab.id === current?.id;
|
|
1091
1092
|
return /* @__PURE__ */ jsx(
|
|
@@ -3773,10 +3774,13 @@ function RailBody({ rail, railHead, railLabel }) {
|
|
|
3773
3774
|
] }) : /* @__PURE__ */ jsx("div", { className: "caos-wb-rail-panel", children: rail })
|
|
3774
3775
|
] });
|
|
3775
3776
|
}
|
|
3776
|
-
function WorkbenchLayout({ rail, railHead, children, railLabel, className }) {
|
|
3777
|
+
function WorkbenchLayout({ rail, railHead, children, railLabel, className, railWidth }) {
|
|
3777
3778
|
const lang = useShellLang();
|
|
3778
3779
|
const label = pick(railLabel ?? { en: "Controls", es: "Controles" }, lang);
|
|
3779
|
-
|
|
3780
|
+
if (railWidth !== void 0 && (!Number.isFinite(railWidth) || railWidth < 150 || railWidth > 360)) {
|
|
3781
|
+
throw new RangeError("WorkbenchLayout.railWidth must be a finite value from 150 to 360 px");
|
|
3782
|
+
}
|
|
3783
|
+
return /* @__PURE__ */ jsxs("div", { className: ["page-body", "wide", "caos-wb", className].filter(Boolean).join(" "), style: railWidth === void 0 ? void 0 : { "--rail-w": `${railWidth}px` }, "data-workbench": "", "data-rail-width": railWidth, children: [
|
|
3780
3784
|
/* @__PURE__ */ jsx("aside", { className: "caos-wb-rail", "aria-label": label, "data-rail": "", children: /* @__PURE__ */ jsx(RailBody, { rail, railHead, railLabel }) }),
|
|
3781
3785
|
/* @__PURE__ */ jsx("section", { className: "caos-wb-main", "data-instrument": "", children })
|
|
3782
3786
|
] });
|
|
@@ -3857,7 +3861,7 @@ function panel(id, lane, provenance, content) {
|
|
|
3857
3861
|
return /* @__PURE__ */ jsx("div", { className: "caos-cw-panel", "data-group": id, "data-lane": lane, "data-provenance": provenance, children: content });
|
|
3858
3862
|
}
|
|
3859
3863
|
function CaseWorkbench(props) {
|
|
3860
|
-
const { caseId, cases, source, controls, replayOnly, noteFit, variants, rail, railLabel, groups, compare, context, group, onGroupChange, deepLinkView = true, ariaLabel, focus } = props;
|
|
3864
|
+
const { caseId, cases, source, controls, replayOnly, noteFit, variants, rail, railLabel, railWidth, tabDensity = "comfortable", groups, compare, context, group, onGroupChange, deepLinkView = true, ariaLabel, focus } = props;
|
|
3861
3865
|
const lang = useShellLang();
|
|
3862
3866
|
const focusCfg = focus === true ? {} : focus || null;
|
|
3863
3867
|
const { show: showFocus, setFocus, enterRef, memory } = useFocusMode(focusCfg);
|
|
@@ -3937,14 +3941,15 @@ function CaseWorkbench(props) {
|
|
|
3937
3941
|
}
|
|
3938
3942
|
) }) });
|
|
3939
3943
|
}
|
|
3940
|
-
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory, children: /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(WorkbenchLayout, { rail: rail ?? null, railHead, railLabel, children: /* @__PURE__ */ jsx("div", { className: "caos-cw", ...instrumentData, children: /* @__PURE__ */ jsx(
|
|
3944
|
+
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory, children: /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(WorkbenchLayout, { rail: rail ?? null, railHead, railLabel, railWidth, children: /* @__PURE__ */ jsx("div", { className: "caos-cw", ...instrumentData, children: /* @__PURE__ */ jsx(
|
|
3941
3945
|
Tabs,
|
|
3942
3946
|
{
|
|
3943
3947
|
tabs,
|
|
3944
3948
|
value: openGroup,
|
|
3945
3949
|
onChange: selectGroup,
|
|
3946
3950
|
ariaLabel: pick(ariaLabel ?? { en: "Case views", es: "Vistas del caso" }, lang),
|
|
3947
|
-
trailing: enter || void 0
|
|
3951
|
+
trailing: enter || void 0,
|
|
3952
|
+
density: tabDensity
|
|
3948
3953
|
}
|
|
3949
3954
|
) }) }) }) });
|
|
3950
3955
|
}
|
|
@@ -94,6 +94,8 @@ interface ShellConfig {
|
|
|
94
94
|
};
|
|
95
95
|
/** Routes whose surface IS the viewport (ADR-0071 rule 1), matched exactly or by prefix for a non-root path. */
|
|
96
96
|
fixedRoutes?: string[];
|
|
97
|
+
/** Routes that use the full frame without site chrome (for a guided, direct-link StoryFrame). */
|
|
98
|
+
immersiveRoutes?: string[];
|
|
97
99
|
/** Every route fixed: a single-surface app (a hub, a one-page tool). */
|
|
98
100
|
fixed?: boolean;
|
|
99
101
|
/**
|
package/dist/keys.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as APP_ROUTE, L as LANDING_ROUTE, e as LANG_STORAGE_KEY, S as STANDARD_ROUTES, T as THEME_BOOT_SCRIPT, h as THEME_STORAGE_KEY } from './keys-
|
|
1
|
+
export { A as APP_ROUTE, L as LANDING_ROUTE, e as LANG_STORAGE_KEY, S as STANDARD_ROUTES, T as THEME_BOOT_SCRIPT, h as THEME_STORAGE_KEY } from './keys-D-pSo8hG.js';
|
|
2
2
|
import 'react';
|
|
3
3
|
import './text-BlY47nIt.js';
|
|
4
4
|
import 'zustand';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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
|
@@ -288,6 +288,7 @@
|
|
|
288
288
|
"header-sep",
|
|
289
289
|
"hint",
|
|
290
290
|
"icon-btn",
|
|
291
|
+
"immersive",
|
|
291
292
|
"katex",
|
|
292
293
|
"l-en",
|
|
293
294
|
"l-es",
|
|
@@ -321,6 +322,7 @@
|
|
|
321
322
|
"tablist",
|
|
322
323
|
"tabpanel",
|
|
323
324
|
"tabs",
|
|
325
|
+
"tabs-compact",
|
|
324
326
|
"tabs-has-slot",
|
|
325
327
|
"tabs-slot",
|
|
326
328
|
"th-refs",
|
|
@@ -652,6 +654,7 @@
|
|
|
652
654
|
"header-sep",
|
|
653
655
|
"hint",
|
|
654
656
|
"icon-btn",
|
|
657
|
+
"immersive",
|
|
655
658
|
"is-active",
|
|
656
659
|
"katex",
|
|
657
660
|
"l-en",
|
|
@@ -692,6 +695,7 @@
|
|
|
692
695
|
"tablist",
|
|
693
696
|
"tabpanel",
|
|
694
697
|
"tabs",
|
|
698
|
+
"tabs-compact",
|
|
695
699
|
"tabs-has-slot",
|
|
696
700
|
"tabs-slot",
|
|
697
701
|
"th-refs",
|
package/styles.css
CHANGED
|
@@ -201,8 +201,11 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
201
201
|
.app-shell.fixed .site-footer { margin-top: 0; }
|
|
202
202
|
.app-shell.fixed .site-footer .footer-inner { padding: 0.45rem 1.5rem; gap: 0.4rem; font-size: 0.74rem; line-height: 1.3; }
|
|
203
203
|
.app-shell.fixed.contain > .page { display: flex; flex-direction: column; overflow: hidden; }
|
|
204
|
+
.app-shell.immersive { height: 100dvh; min-height: 100dvh; overflow: hidden; }
|
|
205
|
+
.app-shell.immersive > .page { display: flex; flex: 1 1 auto; min-height: 0; overflow: hidden; padding: 0; }
|
|
206
|
+
.app-shell.immersive > .page > .caos-story { width: 100%; height: 100%; flex: 1 1 auto; }
|
|
204
207
|
/* A document route scrolls inside the main container; a workbench or a surface fills it. */
|
|
205
|
-
.app-shell.fixed.contain > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow-y: auto; overscroll-behavior: contain; }
|
|
208
|
+
.app-shell.fixed.contain:not(.immersive) > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow-y: auto; overscroll-behavior: contain; }
|
|
206
209
|
/* The chrome of a contained app spans the instrument's width with its inset, so at 2560 px the header and footer do
|
|
207
210
|
not float in a 1200 px column above a full-width workbench. */
|
|
208
211
|
.app-shell.fixed.contain .header-inner, .app-shell.fixed.contain .site-footer .footer-inner { max-width: var(--maxw-wide); padding-inline: var(--space-4); }
|
|
@@ -213,7 +216,9 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
213
216
|
.app-shell.fixed.contain { height: auto; min-height: 100dvh; overflow: visible; }
|
|
214
217
|
/* Both forms of the rule above are released: the `:has()` form outranked a plain `> .page` and kept a document's
|
|
215
218
|
`.page` a scroll container that never scrolls, so nothing in it could be sticky (G12, 0.8.0). */
|
|
216
|
-
.app-shell.fixed.contain > .page, .app-shell.fixed.contain > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow: visible; }
|
|
219
|
+
.app-shell.fixed.contain:not(.immersive) > .page, .app-shell.fixed.contain:not(.immersive) > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow: visible; }
|
|
220
|
+
/* A stacked phone workbench is a document: its full instrument and controls must remain reachable by page scroll. */
|
|
221
|
+
.app-shell.fixed.contain:not(.immersive) > .page:has(> .caos-wb) { display: block; overflow: visible; }
|
|
217
222
|
}
|
|
218
223
|
@media (max-width: 760px) {
|
|
219
224
|
/* ADR-0071: the route row shares the header row and owns the horizontal scroll; no second row above the
|
|
@@ -253,6 +258,18 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
253
258
|
.page-body.wide.caos-wb { padding-inline: var(--space-4); }
|
|
254
259
|
.caos-wb > * { min-width: 0; }
|
|
255
260
|
.app-shell.fixed.contain > .page > .caos-wb { flex: 1 1 auto; min-height: 0; }
|
|
261
|
+
/* A tuned rail is an API value; keep the compact desktop spacing and control labels within it. */
|
|
262
|
+
.caos-wb[data-rail-width] { gap: 0.4rem; }
|
|
263
|
+
.caos-wb[data-rail-width] .caos-wb-rail,
|
|
264
|
+
.caos-wb[data-rail-width] .caos-wb-rail-head,
|
|
265
|
+
.caos-wb[data-rail-width] .caos-wb-rail-panel { gap: 0.25rem; }
|
|
266
|
+
.caos-wb[data-rail-width] .caos-wb-rail { padding: 0.4rem; }
|
|
267
|
+
.caos-wb[data-rail-width] .caos-readout-row { gap: 0.2rem; }
|
|
268
|
+
.caos-wb[data-rail-width] .tablist { padding-bottom: 0; margin-bottom: 0; }
|
|
269
|
+
.caos-wb[data-rail-width] .tablist .tab { padding-block: 0.2rem; }
|
|
270
|
+
.caos-wb[data-rail-width] .subtabs { margin-block: 0.1rem; }
|
|
271
|
+
.caos-wb[data-rail-width] .subtablist { margin-bottom: 0; }
|
|
272
|
+
.caos-wb[data-rail-width] .subtab { padding-block: 0.18rem; }
|
|
256
273
|
|
|
257
274
|
/* Surface (SurfacePage): a tab App that fills the viewport; its head and tab row hold their height and the open
|
|
258
275
|
panel scrolls. The containment 15 to 19 products wrote for themselves (audit 2026-10-07, section 3). */
|
|
@@ -297,6 +314,8 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
297
314
|
.tablist::-webkit-scrollbar { display: none; }
|
|
298
315
|
.tablist .tab, .tablist button { flex: 0 0 auto; white-space: nowrap; }
|
|
299
316
|
.tab { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: var(--radius-md); background: transparent; color: var(--color-fg-subtle); font-size: var(--text-md); font-weight: 550; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
|
|
317
|
+
.tabs-compact > .tablist { margin-bottom: 0.15rem; padding-bottom: 0; }
|
|
318
|
+
.tabs-compact > .tablist .tab { padding-block: 0.2rem; }
|
|
300
319
|
.tab:hover { color: var(--color-fg); background: var(--color-surface-2); }
|
|
301
320
|
.tab.active { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 35%, transparent); font-weight: 650; }
|
|
302
321
|
.tabpanel:focus, .subtabpanel:focus { outline: none; }
|
|
@@ -573,6 +592,8 @@ svg.lucide { flex: none; }
|
|
|
573
592
|
.caos-cw > .tabs { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
|
|
574
593
|
.caos-cw > .tabs > .tablist { margin-bottom: 0.5rem; padding-bottom: var(--space-1); }
|
|
575
594
|
.caos-cw > .tabs > .tablist .tab { padding: 0.38rem 0.85rem; }
|
|
595
|
+
.caos-cw > .tabs.tabs-compact > .tablist { margin-bottom: 0.15rem; padding-bottom: 0; }
|
|
596
|
+
.caos-cw > .tabs.tabs-compact > .tablist .tab { padding-block: 0.2rem; }
|
|
576
597
|
.caos-cw > .tabs > .tabpanel:not([hidden]) { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
|
|
577
598
|
.caos-cw-panel { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; flex: 1 1 auto; min-height: 0; }
|
|
578
599
|
.caos-cw-panel-head { display: flex; justify-content: flex-end; }
|
|
@@ -641,6 +662,11 @@ svg.lucide { flex: none; }
|
|
|
641
662
|
.caos-plot-note { margin: 0.4rem 0 0; font-size: 0.78rem; color: var(--color-fg-faint); }
|
|
642
663
|
.caos-plot.fill { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
|
|
643
664
|
.caos-plot.fill > .caos-plot-body { flex: 1 1 0; min-height: 0; overflow: hidden; }
|
|
665
|
+
.caos-plot.fill > .caos-plot-body:has(> [data-caos-plot-control]) { display: flex; flex-direction: column; }
|
|
666
|
+
.caos-plot.fill > .caos-plot-body:has(> [data-caos-plot-control]) > .caos-stage { flex: 1 1 0; height: auto; min-height: 0; }
|
|
667
|
+
.caos-plot.fill > .caos-plot-body > [data-caos-plot-control] { flex: 0 0 auto; display: flex; align-items: center; flex-wrap: wrap; gap: 0.35rem 0.6rem; min-width: 0; min-height: 2rem; padding: 0.2rem 0.35rem; color: var(--color-fg-subtle); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
|
|
668
|
+
.caos-plot.fill > .caos-plot-body > [data-caos-plot-control] input { flex: 1 1 100%; width: 100%; min-width: 8rem; margin: 0; accent-color: var(--color-accent); }
|
|
669
|
+
.caos-plot.fill > .caos-plot-body > [data-caos-plot-control] strong { color: var(--color-fg); font-weight: 600; }
|
|
644
670
|
.caos-plot.fill > .caos-plot-body > .caos-table { height: 100%; }
|
|
645
671
|
/* The note of a filling card keeps at most 35% of the card and scrolls beyond it, so the drawing keeps a floor
|
|
646
672
|
(CAOS_APP_SHELL#95: a long note left plots of 10 to 90 px at 1280 x 800). */
|
|
@@ -953,10 +979,6 @@ svg.lucide { flex: none; }
|
|
|
953
979
|
|
|
954
980
|
/* ADR-0082 showcase frame: one focused stage, one docked panel, readable step navigation and anchored scene notes. */
|
|
955
981
|
.caos-story { display: flex; flex-direction: column; gap: 0; min-width: 0; min-height: 0; height: 100%; padding: 0; color: var(--color-fg); background: var(--color-bg); }
|
|
956
|
-
body:has([data-story]) .app-shell > .site-header,
|
|
957
|
-
body:has([data-story]) .app-shell > .site-footer { display: none !important; }
|
|
958
|
-
body:has([data-story]) .app-shell > .page { display: flex; flex: 1 1 auto; min-height: 0; overflow: hidden; padding: 0; }
|
|
959
|
-
body:has([data-story]) .app-shell > .page > .caos-story { width: 100%; height: 100%; flex: 1 1 auto; }
|
|
960
982
|
.caos-story-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); min-width: 0; }
|
|
961
983
|
.caos-story-heading { min-width: 0; }
|
|
962
984
|
.caos-story-verb { display: block; color: var(--color-step-1); font: 700 var(--text-xs) var(--font-mono); letter-spacing: .09em; text-transform: uppercase; }
|
|
@@ -970,7 +992,8 @@ body:has([data-story]) .app-shell > .page > .caos-story { width: 100%; height: 1
|
|
|
970
992
|
.caos-story-actions,.caos-story-controls { display: flex; align-items: center; gap: var(--space-2); }
|
|
971
993
|
.caos-story button { min-height: 36px; padding: .35rem .65rem; color: var(--color-fg); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); cursor: pointer; }
|
|
972
994
|
.caos-story button:disabled { opacity: .55; cursor: not-allowed; }
|
|
973
|
-
.caos-story-
|
|
995
|
+
.caos-story-actions button { min-height: clamp(28px, 2.5vw, 36px); padding: clamp(.15rem, .5vw, .35rem) clamp(.4rem, .8vw, .65rem); font-size: clamp(.75rem, .8vw, .9rem); line-height: 1.15; white-space: nowrap; }
|
|
996
|
+
.caos-story-stepper { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(120px, 1fr); grid-template-columns: none; gap: var(--space-2); min-width: 0; overflow-x: auto; }
|
|
974
997
|
.caos-story-step { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto; align-items: center; column-gap: .55rem; min-width: 0; min-height: 40px; padding: .2rem .55rem; color: var(--color-fg-subtle); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); text-decoration: none; }
|
|
975
998
|
.caos-story-step:hover { color: var(--color-fg); text-decoration: none; }
|
|
976
999
|
.caos-story-step.is-active { color: var(--color-fg); border-color: var(--color-accent); box-shadow: inset 3px 0 var(--color-accent); }
|
|
@@ -1009,7 +1032,10 @@ body:has([data-story]) .app-shell > .page > .caos-story { width: 100%; height: 1
|
|
|
1009
1032
|
.caos-callout-trend.trend-down { color: var(--color-good); }
|
|
1010
1033
|
.caos-callout-source { grid-column: 1 / -1; }
|
|
1011
1034
|
@media (max-width: 760px) {
|
|
1012
|
-
|
|
1035
|
+
.app-shell.immersive { height: auto; min-height: 100dvh; overflow: visible; }
|
|
1036
|
+
.app-shell.immersive > .page { display: block; overflow: visible; }
|
|
1037
|
+
.app-shell.immersive > .page > .caos-story { height: auto; min-height: 100%; }
|
|
1038
|
+
.app-shell.immersive > .page { overflow: visible; }
|
|
1013
1039
|
.caos-story { height: auto; min-height: 100%; }
|
|
1014
1040
|
.caos-story-header { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; gap: .45rem; }
|
|
1015
1041
|
.caos-story-heading { width: 100%; }
|