@fasl-work/caos-app-shell 0.3.0 → 0.6.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/README.md +12 -1
- package/dist/index.d.ts +40 -2
- package/dist/index.js +52 -3
- package/package.json +59 -56
- package/styles.css +71 -2
package/README.md
CHANGED
|
@@ -60,9 +60,20 @@ const config: ShellConfig = {
|
|
|
60
60
|
|
|
61
61
|
## Exports
|
|
62
62
|
|
|
63
|
-
`AppShell`, `ThemeToggle`, `LanguageToggle`, `useThemeStore`, `applyTheme`, `readTheme`, `useLangStore`,
|
|
63
|
+
`AppShell`, `WorkbenchShell`, `ThemeToggle`, `LanguageToggle`, `useThemeStore`, `applyTheme`, `readTheme`, `useLangStore`,
|
|
64
64
|
`useShellLang`, `usePausedViz`, `createVizLoop`, `CaseSelector` (+ `caseModel` helpers), `Tabs`, `SubTabs`,
|
|
65
65
|
`Callout`, `Equation`, `InlineMath`, `Figure`, `CitationsProvider`, `Cite`, `Refs`, `ReferenceList`, plus
|
|
66
66
|
the `@fasl-work/caos-app-shell/styles.css` design system.
|
|
67
67
|
|
|
68
|
+
### Authenticated workbenches
|
|
69
|
+
|
|
70
|
+
`WorkbenchShell` is the shared frame for products whose authenticated UI needs a desktop sidebar and
|
|
71
|
+
mobile bottom navigation. It renders typed routes and the main landmark while the product supplies its
|
|
72
|
+
own stateful controls through `brand`, `sidebarFooter`, `headerLead`, `headerActions`, and `overlays`.
|
|
73
|
+
Authentication, account policy, preference persistence, chat, and modal content remain product-owned.
|
|
74
|
+
The component uses the stable core `NavLink` contract and supports `react-router` 6, 7, and 8.
|
|
75
|
+
Browser applications on 6/7 install the matching `react-router-dom`, which supplies the core peer;
|
|
76
|
+
Router 8 applications provide `react-router` directly. Do not mix different core and DOM majors in
|
|
77
|
+
one application.
|
|
78
|
+
|
|
68
79
|
MIT · part of the Faena mining-analytics hub.
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,15 @@ interface ArchTab {
|
|
|
10
10
|
/** the explanation body (paragraphs separated by a blank line). */
|
|
11
11
|
body_en: string;
|
|
12
12
|
body_es: string;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* an inline '<svg…>…</svg>' string OR a path under the app's public/ (fetched + inlined).
|
|
15
|
+
*
|
|
16
|
+
* ONE file carries BOTH languages (ADR-0058). Tag each translatable `<text>` twice at the same
|
|
17
|
+
* coordinates, `class="… l-en"` and `class="… l-es"`; the modal wrapper sets `data-arch-lang`
|
|
18
|
+
* and the shell stylesheet shows exactly one. Two files would be two things to keep in step, and
|
|
19
|
+
* the one that is not on screen is the one that goes stale. Anything language-neutral (a number,
|
|
20
|
+
* a file name, an identifier) needs no pair.
|
|
21
|
+
*/
|
|
14
22
|
svg: string;
|
|
15
23
|
}
|
|
16
24
|
interface ArchitectureConfig {
|
|
@@ -68,6 +76,36 @@ declare function AppShell({ config, children }: {
|
|
|
68
76
|
children: ReactNode;
|
|
69
77
|
}): react.JSX.Element;
|
|
70
78
|
|
|
79
|
+
/** One authenticated workbench destination rendered by the shared responsive navigation frame. */
|
|
80
|
+
interface WorkbenchRoute {
|
|
81
|
+
path: string;
|
|
82
|
+
label: ReactNode;
|
|
83
|
+
icon?: ReactNode;
|
|
84
|
+
end?: boolean;
|
|
85
|
+
ariaLabel?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Slots owned by a product around the shared workbench layout.
|
|
89
|
+
*
|
|
90
|
+
* The shell deliberately does not own authentication, preferences, account state, chat, or modal
|
|
91
|
+
* content. Products supply those controls as slots while this package owns the frame, navigation
|
|
92
|
+
* semantics, responsive layout classes, and main-content landmark.
|
|
93
|
+
*/
|
|
94
|
+
interface WorkbenchShellProps {
|
|
95
|
+
brand: ReactNode;
|
|
96
|
+
routes: WorkbenchRoute[];
|
|
97
|
+
navigationLabel: string;
|
|
98
|
+
children: ReactNode;
|
|
99
|
+
sidebarFooter?: ReactNode;
|
|
100
|
+
headerLead?: ReactNode;
|
|
101
|
+
headerActions?: ReactNode;
|
|
102
|
+
overlays?: ReactNode;
|
|
103
|
+
mainId?: string;
|
|
104
|
+
className?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Shared CAOS frame for authenticated consoles with desktop sidebar and mobile bottom navigation. */
|
|
107
|
+
declare function WorkbenchShell({ brand, routes, navigationLabel, children, sidebarFooter, headerLead, headerActions, overlays, mainId, className, }: WorkbenchShellProps): react.JSX.Element;
|
|
108
|
+
|
|
71
109
|
/** Light/dark icon toggle. Shows the icon of the theme you would switch TO. */
|
|
72
110
|
declare function ThemeToggle(): react.JSX.Element;
|
|
73
111
|
|
|
@@ -316,4 +354,4 @@ declare function ReferenceList({ ids, heading }: {
|
|
|
316
354
|
heading?: string;
|
|
317
355
|
}): react.JSX.Element;
|
|
318
356
|
|
|
319
|
-
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, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
|
357
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useState, useRef, useEffect, useCallback, useId, useContext } from 'react';
|
|
2
|
-
import { NavLink } from 'react-router
|
|
2
|
+
import { NavLink } from 'react-router';
|
|
3
3
|
import { Sun, Moon, Languages, Boxes, Github, Globe, Briefcase, Info } from 'lucide-react';
|
|
4
4
|
import { create } from 'zustand';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -223,7 +223,7 @@ function ArchitectureModal({ config, onClose }) {
|
|
|
223
223
|
t.id
|
|
224
224
|
);
|
|
225
225
|
}) }),
|
|
226
|
-
/* @__PURE__ */ jsxs("div", { role: "tabpanel", style: { overflowY: "auto", padding: "14px 16px 18px" }, children: [
|
|
226
|
+
/* @__PURE__ */ jsxs("div", { role: "tabpanel", "data-arch-lang": es ? "es" : "en", style: { overflowY: "auto", padding: "14px 16px 18px" }, children: [
|
|
227
227
|
body.map((p, i) => /* @__PURE__ */ jsx("p", { style: { color: "var(--color-fg)", fontSize: 13, lineHeight: 1.65, margin: i === 0 ? "0 0 10px" : "10px 0" }, children: p }, i)),
|
|
228
228
|
err && /* @__PURE__ */ jsxs("div", { style: { color: "var(--color-warn, #f87171)", fontSize: 12 }, children: [
|
|
229
229
|
"SVG: ",
|
|
@@ -308,6 +308,55 @@ function AppShell({ config, children }) {
|
|
|
308
308
|
] }) }) })
|
|
309
309
|
] });
|
|
310
310
|
}
|
|
311
|
+
function WorkbenchShell({
|
|
312
|
+
brand,
|
|
313
|
+
routes,
|
|
314
|
+
navigationLabel,
|
|
315
|
+
children,
|
|
316
|
+
sidebarFooter,
|
|
317
|
+
headerLead,
|
|
318
|
+
headerActions,
|
|
319
|
+
overlays,
|
|
320
|
+
mainId = "main-content",
|
|
321
|
+
className
|
|
322
|
+
}) {
|
|
323
|
+
const rootClassName = ["caos-workbench-shell", "app-shell", className].filter(Boolean).join(" ");
|
|
324
|
+
return /* @__PURE__ */ jsxs("div", { className: rootClassName, children: [
|
|
325
|
+
/* @__PURE__ */ jsxs(
|
|
326
|
+
"aside",
|
|
327
|
+
{
|
|
328
|
+
className: "caos-workbench-sidebar sidebar",
|
|
329
|
+
style: { "--caos-workbench-route-count": routes.length },
|
|
330
|
+
children: [
|
|
331
|
+
brand,
|
|
332
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": navigationLabel, children: routes.map((route) => /* @__PURE__ */ jsxs(
|
|
333
|
+
NavLink,
|
|
334
|
+
{
|
|
335
|
+
to: route.path,
|
|
336
|
+
end: route.end ?? route.path === "/",
|
|
337
|
+
"aria-label": route.ariaLabel,
|
|
338
|
+
className: ({ isActive }) => isActive ? "active" : void 0,
|
|
339
|
+
children: [
|
|
340
|
+
route.icon,
|
|
341
|
+
/* @__PURE__ */ jsx("span", { children: route.label })
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
route.path
|
|
345
|
+
)) }),
|
|
346
|
+
sidebarFooter
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsxs("div", { className: "caos-workbench-main shell-main", children: [
|
|
351
|
+
/* @__PURE__ */ jsxs("header", { className: "caos-workbench-header topbar", children: [
|
|
352
|
+
headerLead,
|
|
353
|
+
headerActions
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ jsx("main", { id: mainId, className: "caos-workbench-content content", children })
|
|
356
|
+
] }),
|
|
357
|
+
overlays
|
|
358
|
+
] });
|
|
359
|
+
}
|
|
311
360
|
|
|
312
361
|
// src/lib/vizLoop.ts
|
|
313
362
|
function createVizLoop(frame, options, deps) {
|
|
@@ -788,4 +837,4 @@ function ReferenceList({ ids, heading }) {
|
|
|
788
837
|
] });
|
|
789
838
|
}
|
|
790
839
|
|
|
791
|
-
export { AppShell, ArchitectureModal, Callout, CaseSelector, CitationsProvider, Cite, Equation, Figure, InlineMath, LanguageToggle, ReferenceList, Refs, SubTabs, Tabs, ThemeToggle, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
|
|
840
|
+
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 };
|
package/package.json
CHANGED
|
@@ -1,56 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared web-app shell + content primitives + design system for the CAOS / Faena public apps (ADR-0016).",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Felipe Santibáñez-Leal",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/fsantibanezleal/CAOS_APP_SHELL.git"
|
|
10
|
-
},
|
|
11
|
-
"type": "module",
|
|
12
|
-
"publishConfig": {
|
|
13
|
-
"access": "public"
|
|
14
|
-
},
|
|
15
|
-
"sideEffects": [
|
|
16
|
-
"*.css"
|
|
17
|
-
],
|
|
18
|
-
"exports": {
|
|
19
|
-
".": {
|
|
20
|
-
"types": "./dist/index.d.ts",
|
|
21
|
-
"import": "./dist/index.js"
|
|
22
|
-
},
|
|
23
|
-
"./styles.css": "./styles.css"
|
|
24
|
-
},
|
|
25
|
-
"files": [
|
|
26
|
-
"dist",
|
|
27
|
-
"styles.css"
|
|
28
|
-
],
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsup",
|
|
31
|
-
"test": "node --import tsx --test test
|
|
32
|
-
"prepublishOnly": "npm run build"
|
|
33
|
-
},
|
|
34
|
-
"peerDependencies": {
|
|
35
|
-
"katex": "^0.16.0",
|
|
36
|
-
"lucide-react": ">=0.400.0",
|
|
37
|
-
"react": "^18 || ^19",
|
|
38
|
-
"react-dom": "^18 || ^19",
|
|
39
|
-
"react-router
|
|
40
|
-
"zustand": "^4 || ^5"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@types/katex": "^0.16.7",
|
|
44
|
-
"@types/react": "^19.0.0",
|
|
45
|
-
"@types/react-dom": "^19.0.0",
|
|
46
|
-
"katex": "^0.16.11",
|
|
47
|
-
"lucide-react": "^0.469.0",
|
|
48
|
-
"react": "^19.0.0",
|
|
49
|
-
"react-dom": "^19.0.0",
|
|
50
|
-
"react-router
|
|
51
|
-
"tsup": "^8.3.5",
|
|
52
|
-
"tsx": "^4.23.0",
|
|
53
|
-
"typescript": "^5.7.3",
|
|
54
|
-
"zustand": "^5.0.2"
|
|
55
|
-
}
|
|
56
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@fasl-work/caos-app-shell",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Shared web-app shell + content primitives + design system for the CAOS / Faena public apps (ADR-0016).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Felipe Santibáñez-Leal",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/fsantibanezleal/CAOS_APP_SHELL.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"*.css"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./styles.css": "./styles.css"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"styles.css"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\" \"test/**/*.test.tsx\"",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"katex": "^0.16.0",
|
|
36
|
+
"lucide-react": ">=0.400.0",
|
|
37
|
+
"react": "^18 || ^19",
|
|
38
|
+
"react-dom": "^18 || ^19",
|
|
39
|
+
"react-router": "^6 || ^7 || ^8",
|
|
40
|
+
"zustand": "^4 || ^5"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/katex": "^0.16.7",
|
|
44
|
+
"@types/react": "^19.0.0",
|
|
45
|
+
"@types/react-dom": "^19.0.0",
|
|
46
|
+
"katex": "^0.16.11",
|
|
47
|
+
"lucide-react": "^0.469.0",
|
|
48
|
+
"react": "^19.0.0",
|
|
49
|
+
"react-dom": "^19.0.0",
|
|
50
|
+
"react-router": "^8.3.0",
|
|
51
|
+
"tsup": "^8.3.5",
|
|
52
|
+
"tsx": "^4.23.0",
|
|
53
|
+
"typescript": "^5.7.3",
|
|
54
|
+
"zustand": "^5.0.2"
|
|
55
|
+
},
|
|
56
|
+
"overrides": {
|
|
57
|
+
"esbuild": "0.28.1"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/styles.css
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
--color-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 16px rgba(0, 0, 0, 0.45);
|
|
26
26
|
--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
27
27
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
28
|
-
--maxw: 1200px;
|
|
28
|
+
--maxw: 1200px; /* reading width, for PROSE pages */
|
|
29
|
+
--maxw-wide: 100%; /* instrument width, for workbench pages */
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
[data-theme="light"] {
|
|
@@ -69,6 +70,10 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
69
70
|
.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: 1.5rem; }
|
|
70
71
|
.page { flex: 1 1 auto; width: 100%; }
|
|
71
72
|
.page-body { width: 100%; max-width: var(--maxw); margin-inline: auto; padding: 2.25rem 1.5rem 3rem; }
|
|
73
|
+
/* A WORKBENCH is not prose. Capping an instrument at a 1200px reading measure wastes every pixel beyond
|
|
74
|
+
it, which is why the App surfaces across the line render their visualization as a thumbnail on a wide
|
|
75
|
+
display. Prose pages keep the reading width; the App route opts into the full viewport. */
|
|
76
|
+
.page-body.wide { max-width: var(--maxw-wide); padding-inline: 1.25rem; }
|
|
72
77
|
.muted { color: var(--color-fg-subtle); }
|
|
73
78
|
.faint { color: var(--color-fg-faint); }
|
|
74
79
|
.mono { font-family: var(--font-mono); font-size: 0.9em; }
|
|
@@ -155,7 +160,18 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
155
160
|
.callout-body p:last-child { margin-bottom: 0; }
|
|
156
161
|
|
|
157
162
|
/* ── Tabs ── */
|
|
158
|
-
.tablist { display: flex; gap: 0.35rem; padding-bottom: 0.6rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--color-border); flex-wrap:
|
|
163
|
+
.tablist { display: flex; gap: 0.35rem; padding-bottom: 0.6rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--color-border); flex-wrap: nowrap; }
|
|
164
|
+
/* SINGLE-LINE TAB BAR.
|
|
165
|
+
`flex-wrap: wrap` lets a 14-to-18 tab bar spill onto a second and third row, and every extra row is
|
|
166
|
+
vertical space taken from the instrument below it, permanently, on every render. A tab bar is
|
|
167
|
+
navigation chrome: it gets ONE row and scrolls horizontally when it does not fit.
|
|
168
|
+
Scrollbar is hidden but the row stays scrollable by wheel, trackpad, drag and keyboard. */
|
|
169
|
+
.tablist { flex-wrap: nowrap !important; overflow-x: auto; overflow-y: hidden;
|
|
170
|
+
scrollbar-width: none; -ms-overflow-style: none; padding-bottom: 0.4rem; margin-bottom: 0.9rem;
|
|
171
|
+
scroll-behavior: smooth; }
|
|
172
|
+
.tablist::-webkit-scrollbar { display: none; }
|
|
173
|
+
.tablist .tab, .tablist button { flex: 0 0 auto; white-space: nowrap; }
|
|
174
|
+
|
|
159
175
|
.tab { padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--color-fg-subtle); font-size: 0.92rem; font-weight: 550; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
|
|
160
176
|
.tab:hover { color: var(--color-fg); background: var(--color-surface-2); }
|
|
161
177
|
.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; }
|
|
@@ -240,3 +256,56 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
240
256
|
.cs-kind-uploaded { color: var(--color-warn); border-color: color-mix(in oklab, var(--color-warn) 40%, transparent); }
|
|
241
257
|
.cs-modified { display: flex; align-items: center; gap: 0.5rem; }
|
|
242
258
|
.cs-reset { border: none; background: transparent; color: var(--color-accent); font-size: 0.8rem; font-weight: 600; padding: 0.1rem 0.2rem; text-decoration: underline; text-underline-offset: 2px; }
|
|
259
|
+
|
|
260
|
+
/* Authenticated workbench frame. Product styles may refine tokens and controls, but the package owns
|
|
261
|
+
the responsive navigation/layout contract and keeps route/content landmarks consistent. */
|
|
262
|
+
.caos-workbench-shell { min-height: 100vh; display: grid; grid-template-columns: 238px minmax(0, 1fr); }
|
|
263
|
+
.caos-workbench-sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; width: 238px; display: flex; flex-direction: column; padding: 1.25rem 0.875rem; border-right: 1px solid var(--color-border); background: var(--color-surface); }
|
|
264
|
+
.caos-workbench-sidebar nav { display: grid; gap: 0.25rem; }
|
|
265
|
+
.caos-workbench-sidebar nav a { display: flex; align-items: center; gap: 0.75rem; padding: 0.625rem 0.7rem; border: 1px solid transparent; border-radius: 0.7rem; color: var(--color-fg-subtle); font-size: 0.9rem; font-weight: 650; }
|
|
266
|
+
.caos-workbench-sidebar nav a:hover { color: var(--color-fg); background: var(--color-surface-2); text-decoration: none; }
|
|
267
|
+
.caos-workbench-sidebar nav a.active { color: var(--color-accent); border-color: color-mix(in oklab, var(--color-accent) 24%, transparent); background: var(--color-accent-soft); }
|
|
268
|
+
.caos-workbench-main { grid-column: 2; min-width: 0; }
|
|
269
|
+
.caos-workbench-header { position: sticky; top: 0; z-index: 15; min-height: 64px; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0 2rem; border-bottom: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-bg) 88%, transparent); backdrop-filter: blur(12px); }
|
|
270
|
+
.caos-workbench-content { width: min(1540px, 100%); margin: 0 auto; padding: 2.25rem 2rem 4rem; }
|
|
271
|
+
|
|
272
|
+
@media (max-width: 760px) {
|
|
273
|
+
.caos-workbench-shell { display: block; padding-bottom: 66px; }
|
|
274
|
+
.caos-workbench-sidebar { inset: auto 0 0; width: auto; height: 62px; display: block; border: 0; border-top: 1px solid var(--color-border); padding: 5px 8px; }
|
|
275
|
+
.caos-workbench-sidebar > :not(nav) { display: none; }
|
|
276
|
+
.caos-workbench-sidebar nav { grid-template-columns: repeat(var(--caos-workbench-route-count, 5), minmax(0, 1fr)); }
|
|
277
|
+
.caos-workbench-sidebar nav a { display: grid; justify-items: center; gap: 2px; overflow: hidden; padding: 5px; font-size: 0.7rem; text-overflow: ellipsis; white-space: nowrap; }
|
|
278
|
+
.caos-workbench-main { min-width: 0; }
|
|
279
|
+
.caos-workbench-header { min-height: 58px; padding: 0 1rem; }
|
|
280
|
+
.caos-workbench-content { padding: 1.5rem 1rem 2.5rem; }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* CONTAINED LAYOUT. A `nowrap` tab bar sizes its container to its intrinsic width unless every
|
|
284
|
+
ancestor is allowed to shrink: a flex/grid item defaults to `min-width: auto`. Without this, making
|
|
285
|
+
the tab bar a single scrollable row pushes the whole page wider than the viewport and the user has to
|
|
286
|
+
drag sideways to reach the right edge. Measured on ChargeCascade: scrollWidth 1817 on a 1600 viewport. */
|
|
287
|
+
html, body { overflow-x: hidden; }
|
|
288
|
+
.page-body { max-width: 100%; }
|
|
289
|
+
.tabs, .tabpanel { min-width: 0; max-width: 100%; }
|
|
290
|
+
.tablist { min-width: 0; max-width: 100%; width: 100%; }
|
|
291
|
+
|
|
292
|
+
/* NO MAGIC CHROME NUMBER. An app surface must not compute its height from a hardcoded guess at the
|
|
293
|
+
header and footer: the guess drifts the moment either changes, leaving either a dead gap above the
|
|
294
|
+
footer or a few pixels of scroll. Measured on ChargeCascade: guess 150px, real 175px (header 57 +
|
|
295
|
+
footer 70 + 48px body padding), so 25px of scroll and a 48px gap. Apps that fill the viewport add
|
|
296
|
+
`.app-shell.fixed` and let flex distribute the height. */
|
|
297
|
+
.app-shell.fixed { height: 100dvh; min-height: 100dvh; overflow: hidden; }
|
|
298
|
+
.app-shell.fixed > .page { flex: 1 1 auto; min-height: 0; display: flex; }
|
|
299
|
+
.app-shell.fixed .page-body.wide { flex: 1 1 auto; min-height: 0; padding-top: 1rem; padding-bottom: 1rem; }
|
|
300
|
+
|
|
301
|
+
/* A viewport-filling app must not carry the prose footer margin: it becomes dead space between the
|
|
302
|
+
content and the chrome. Measured on ChargeCascade as exactly 48px (3rem). */
|
|
303
|
+
.app-shell.fixed .site-footer { margin-top: 0; }
|
|
304
|
+
|
|
305
|
+
/* ADR-0058 diagram i18n. A hand-authored architecture SVG carries BOTH languages inside ONE file,
|
|
306
|
+
and the modal wrapper picks one. Two files would be two things to keep in step, and the one not
|
|
307
|
+
on screen is the one that goes stale. The rules are scoped to [data-arch-lang] so the short class
|
|
308
|
+
names cannot collide with an app's own styles. */
|
|
309
|
+
[data-arch-lang] .l-es { display: none; }
|
|
310
|
+
[data-arch-lang="es"] .l-en { display: none; }
|
|
311
|
+
[data-arch-lang="es"] .l-es { display: inline; }
|