@fasl-work/caos-app-shell 0.14.2 → 0.15.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 +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +62 -3
- package/gate/inpage.mjs +93 -0
- package/gate/run.mjs +7 -1
- package/package.json +1 -1
- package/styles.css +8 -4
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ const config: ShellConfig = {
|
|
|
110
110
|
(G1) or mode (G2), console errors and 4xx/5xx (G3), broken deep links or HTML for an artifact (G4), anything a
|
|
111
111
|
reader cannot scroll to, see whole or click (G5), blank or underfilled drawings and an instrument under half the
|
|
112
112
|
viewport (G6), a view that never settles (G7), loops at rest (G8), controls that change nothing (G9), labels cut or
|
|
113
|
-
overlapping in a drawing, also in a wider fallback font (G10), a decimal point on a Spanish page (G11), a vertical
|
|
113
|
+
overlapping in a drawing, also in a wider fallback font (G10), a decimal point on a Spanish page (G11), a number or percentage broken across lines (G19), a vertical
|
|
114
114
|
sub-tab list that scrolls away (G12), text under WCAG AA contrast (G13, every language on a documentation page), and a documentation page without the
|
|
115
115
|
required areas of its kind (`ADR-0017.2`: executive and technical Introduction, Methodology, Implementation,
|
|
116
116
|
Experiments, Benchmark; declared with `<DocSection area="...">`, waived only with a reason of 12 letters or digits in two
|
package/dist/index.d.ts
CHANGED
|
@@ -69,7 +69,16 @@ interface FocusShellProps {
|
|
|
69
69
|
*/
|
|
70
70
|
landmark?: boolean;
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Full-viewport, scenario-scoped analysis frame. The product supplies only its instrument and controls.
|
|
74
|
+
*
|
|
75
|
+
* The frame measures its overlay chrome and sets three lengths on `.caos-focus-stage` (custom properties, so the instrument
|
|
76
|
+
* inherits them): `--caos-focus-clear-top` (the stage's top edge to the lowest chrome box in its upper half, plus 8px),
|
|
77
|
+
* `--caos-focus-clear-bottom` (likewise from the bottom edge, 0 or unset when nothing sits in the lower half) and
|
|
78
|
+
* `--caos-focus-label-bottom` (the label's bottom edge from the stage's top). A product that renders its own instrument keeps
|
|
79
|
+
* its controls clear of the chrome with `padding-top: var(--caos-focus-clear-top, 4.6rem)`. Before the first measurement and in
|
|
80
|
+
* a non-browser render the properties are unset and the CSS fallbacks apply.
|
|
81
|
+
*/
|
|
73
82
|
declare function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel, landmark }: FocusShellProps): react.JSX.Element;
|
|
74
83
|
|
|
75
84
|
/** Light/dark icon toggle. Shows the icon of the theme you would switch TO. */
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStage
|
|
|
2
2
|
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, formatNumber, formatTicks, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-YECZXGVG.js';
|
|
3
3
|
import { APP_ROUTE, LANDING_ROUTE } from './chunk-33WTUQ7Q.js';
|
|
4
4
|
export { APP_ROUTE, LANDING_ROUTE, LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-33WTUQ7Q.js';
|
|
5
|
-
import { createContext, Component, useState, useRef, useId, useEffect, useCallback, useContext, useMemo,
|
|
5
|
+
import { createContext, Component, useState, useRef, useId, useEffect, useLayoutEffect, useCallback, useContext, useMemo, Children } from 'react';
|
|
6
6
|
import { useInRouterContext, NavLink, useLocation, Navigate, Link } from 'react-router';
|
|
7
7
|
import { Sun, Moon, Languages, Boxes, CodeXml, Globe, Briefcase, Info, ChevronLeft, ChevronRight, Maximize2, RotateCw } from 'lucide-react';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -483,10 +483,65 @@ function WorkbenchShell({
|
|
|
483
483
|
overlays
|
|
484
484
|
] });
|
|
485
485
|
}
|
|
486
|
+
var GAP = 8;
|
|
487
|
+
function measureChrome(stage) {
|
|
488
|
+
const box = stage.getBoundingClientRect();
|
|
489
|
+
if (box.height <= 0 || box.width <= 0) return;
|
|
490
|
+
const label = stage.querySelector(":scope > .caos-focus-label");
|
|
491
|
+
const chrome2 = [
|
|
492
|
+
label,
|
|
493
|
+
...Array.from(stage.querySelectorAll(":scope > .caos-focus-hud > div")),
|
|
494
|
+
stage.querySelector(":scope > .caos-focus-actions")
|
|
495
|
+
].filter((e) => e !== null);
|
|
496
|
+
const write = (name, px) => {
|
|
497
|
+
const next = px === null ? "" : `${px}px`;
|
|
498
|
+
if (stage.style.getPropertyValue(name) === next) return false;
|
|
499
|
+
if (px === null) stage.style.removeProperty(name);
|
|
500
|
+
else stage.style.setProperty(name, next);
|
|
501
|
+
return true;
|
|
502
|
+
};
|
|
503
|
+
const labelBottom = label ? Math.ceil(label.getBoundingClientRect().bottom - box.top) : null;
|
|
504
|
+
const moved = write("--caos-focus-label-bottom", labelBottom !== null && labelBottom > 0 ? labelBottom : null);
|
|
505
|
+
if (moved) {
|
|
506
|
+
measureChrome(stage);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const mid = box.height / 2;
|
|
510
|
+
let top = 0;
|
|
511
|
+
let bottom = 0;
|
|
512
|
+
for (const el of chrome2) {
|
|
513
|
+
const r = el.getBoundingClientRect();
|
|
514
|
+
if (r.width <= 0 || r.height <= 0) continue;
|
|
515
|
+
if (r.top - box.top < mid) top = Math.max(top, r.bottom - box.top + GAP);
|
|
516
|
+
else bottom = Math.max(bottom, box.bottom - r.top + GAP);
|
|
517
|
+
}
|
|
518
|
+
write("--caos-focus-clear-top", top > 0 ? Math.ceil(top) : null);
|
|
519
|
+
write("--caos-focus-clear-bottom", bottom > 0 ? Math.ceil(bottom) : null);
|
|
520
|
+
}
|
|
486
521
|
function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel, landmark = true }) {
|
|
487
522
|
const Root = landmark ? "main" : "div";
|
|
523
|
+
const stageRef = useRef(null);
|
|
524
|
+
useLayoutEffect(() => {
|
|
525
|
+
const el = stageRef.current;
|
|
526
|
+
if (el) measureChrome(el);
|
|
527
|
+
});
|
|
528
|
+
useLayoutEffect(() => {
|
|
529
|
+
const el = stageRef.current;
|
|
530
|
+
if (!el) return void 0;
|
|
531
|
+
const run = () => measureChrome(el);
|
|
532
|
+
window.addEventListener("resize", run);
|
|
533
|
+
let ro;
|
|
534
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
535
|
+
ro = new ResizeObserver(run);
|
|
536
|
+
for (const c of Array.from(el.querySelectorAll(":scope > .caos-focus-label, :scope > .caos-focus-hud, :scope > .caos-focus-actions"))) ro.observe(c);
|
|
537
|
+
}
|
|
538
|
+
return () => {
|
|
539
|
+
window.removeEventListener("resize", run);
|
|
540
|
+
ro?.disconnect();
|
|
541
|
+
};
|
|
542
|
+
}, []);
|
|
488
543
|
return /* @__PURE__ */ jsxs(Root, { className: "caos-focus-shell", "data-focus-shell": "", children: [
|
|
489
|
-
/* @__PURE__ */ jsxs("section", { className: "caos-focus-stage", "aria-label": stageLabel, children: [
|
|
544
|
+
/* @__PURE__ */ jsxs("section", { ref: stageRef, className: "caos-focus-stage", "aria-label": stageLabel, children: [
|
|
490
545
|
/* @__PURE__ */ jsx("div", { className: "caos-focus-instrument", children: stage }),
|
|
491
546
|
/* @__PURE__ */ jsxs("div", { className: "caos-focus-label", children: [
|
|
492
547
|
/* @__PURE__ */ jsx("strong", { children: title }),
|
|
@@ -3355,6 +3410,10 @@ function CaseWorkbench(props) {
|
|
|
3355
3410
|
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases, accessory: besideSelect ? enter : cases.accessory }),
|
|
3356
3411
|
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
3357
3412
|
] }) : null;
|
|
3413
|
+
const focusPickers = cases || variants ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3414
|
+
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases, accessory: cases.accessory }),
|
|
3415
|
+
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
3416
|
+
] }) : null;
|
|
3358
3417
|
const railHead = pickers || enter ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3359
3418
|
pickers,
|
|
3360
3419
|
besideSelect ? null : enter
|
|
@@ -3382,7 +3441,7 @@ function CaseWorkbench(props) {
|
|
|
3382
3441
|
stageLabel: FOCUS_TEXT.stage[lang],
|
|
3383
3442
|
landmark: false,
|
|
3384
3443
|
stage: /* @__PURE__ */ jsx("div", { className: "caos-cw caos-cw-focus", ...instrumentData, "data-focus-group": open?.id, "data-instrument": "", children: /* @__PURE__ */ jsx("div", { className: "tabs", children: open && /* @__PURE__ */ jsx("div", { className: "tabpanel", role: "tabpanel", tabIndex: 0, "aria-label": open.label, "data-panel": open.id, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: open.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: open.content }) }) }) }) }),
|
|
3385
|
-
rail: /* @__PURE__ */ jsx("div", { className: "caos-focus-rail-body", children: /* @__PURE__ */ jsx(RailBody, { rail: rail ?? null, railHead:
|
|
3444
|
+
rail: /* @__PURE__ */ jsx("div", { className: "caos-focus-rail-body", children: /* @__PURE__ */ jsx(RailBody, { rail: rail ?? null, railHead: focusPickers, railLabel }) })
|
|
3386
3445
|
}
|
|
3387
3446
|
) }) });
|
|
3388
3447
|
}
|
package/gate/inpage.mjs
CHANGED
|
@@ -1036,8 +1036,32 @@ export function installLib() {
|
|
|
1036
1036
|
const panel = stage ? stage.querySelector('[role="tabpanel"]') : null;
|
|
1037
1037
|
const st = document.querySelector('.caos-focus-stage');
|
|
1038
1038
|
const box = st ? st.getBoundingClientRect() : null;
|
|
1039
|
+
// a stage control the Focus chrome (the label, the HUD tiles, the actions) sits on: more than 1px in both directions
|
|
1040
|
+
const chrome = st
|
|
1041
|
+
? [
|
|
1042
|
+
...[...st.querySelectorAll(':scope > .caos-focus-label')].map((e) => [e, 'the stage label']),
|
|
1043
|
+
...[...st.querySelectorAll(':scope > .caos-focus-hud > div')].map((e) => [e, 'the HUD']),
|
|
1044
|
+
...[...st.querySelectorAll(':scope > .caos-focus-actions')].map((e) => [e, 'the Focus actions']),
|
|
1045
|
+
].filter(([e]) => visible(e))
|
|
1046
|
+
: [];
|
|
1047
|
+
const covered = [];
|
|
1048
|
+
const controlSel = '[role="tab"], button, a[href], select, input, textarea, summary, [role="button"], [role="radio"], [role="checkbox"], [role="switch"], [role="slider"]';
|
|
1049
|
+
for (const c of document.querySelectorAll('.caos-focus-instrument')) {
|
|
1050
|
+
for (const el of c.querySelectorAll(controlSel)) {
|
|
1051
|
+
if (covered.length >= 4) break;
|
|
1052
|
+
if (!visible(el)) continue;
|
|
1053
|
+
const r = el.getBoundingClientRect();
|
|
1054
|
+
const hit = chrome.find(([e]) => {
|
|
1055
|
+
const b = e.getBoundingClientRect();
|
|
1056
|
+
return r.left < b.right - 1 && r.right > b.left + 1 && r.top < b.bottom - 1 && r.bottom > b.top + 1;
|
|
1057
|
+
});
|
|
1058
|
+
if (hit) covered.push({ control: (el.getAttribute('aria-label') || el.textContent || el.tagName).trim().replace(/\s+/g, ' ').slice(0, 40), chrome: hit[1] });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1039
1061
|
return {
|
|
1040
1062
|
focus: Boolean(shell),
|
|
1063
|
+
covered,
|
|
1064
|
+
enterInside: [...document.querySelectorAll('[data-focus-shell] [data-focus-enter]')].filter(visible).length,
|
|
1041
1065
|
app: Boolean(document.querySelector('[data-workbench]')),
|
|
1042
1066
|
caseId: stage ? stage.getAttribute('data-case') : null,
|
|
1043
1067
|
group: shell ? (stage ? stage.getAttribute('data-focus-group') : null) : panel ? panel.getAttribute('data-panel') : null,
|
|
@@ -1257,6 +1281,75 @@ export function installLib() {
|
|
|
1257
1281
|
return [...new Set(out)].slice(0, 12);
|
|
1258
1282
|
};
|
|
1259
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* G19, values whole (BL-065): a short value written in the page's text sits on one line. Shell 0.14.2 fixed a share
|
|
1286
|
+
* "35%" that broke into "3", "5", "%" in a squeezed flex row; no other check sees it, because the pieces are neither
|
|
1287
|
+
* clipped nor outside their box. A short value is a run of characters without breaking whitespace (U+0020, tab, CR,
|
|
1288
|
+
* LF; U+00A0 and U+202F do not end a run) inside one text node, 2 to 16 characters long once the punctuation of the
|
|
1289
|
+
* sentence is stripped from its ends, with a digit, only digits, signs (the hyphens and dashes included), separators,
|
|
1290
|
+
* currency signs, % per mille x and degree signs and at most 4 letters (35%, 0,96, US$1.2bn, 2026-10-15, -3,2pp, 1e-6,
|
|
1291
|
+
* ADR-0071). A value is cut into segments right after a hyphen (U+002D, U+2010, U+2011), an en or em dash and a slash;
|
|
1292
|
+
* a break at those places is a normal line break, so the pieces between them are what must stay whole. A leading sign
|
|
1293
|
+
* stays in the first segment (a lone "-" apart from "3,2" is a broken value). The lines of a segment of 2 or more
|
|
1294
|
+
* characters are the groups of its Range's client rects by top (2px); a segment on more than one line fails the value,
|
|
1295
|
+
* also when it is longer than its container: a segment is never cut into pieces. Two values apart by a regular space
|
|
1296
|
+
* ("12 %") are two runs and may wrap between them. Limits: text nodes only (a value split across elements, <b>35</b>%,
|
|
1297
|
+
* is not read); SVG text is G10's
|
|
1298
|
+
* subject and canvas text cannot be read; code, formulas and anything marked translate="no" are skipped.
|
|
1299
|
+
*/
|
|
1300
|
+
G.brokenValues = () => {
|
|
1301
|
+
const out = [];
|
|
1302
|
+
const skip = 'code, pre, kbd, samp, .katex, svg, canvas, textarea, [translate="no"], [aria-hidden="true"], script, style';
|
|
1303
|
+
const runRe = /[^ \t\r\n]+/g;
|
|
1304
|
+
const BREAK_AFTER = '-\u2010\u2011\u2013\u2014/';
|
|
1305
|
+
const strip = /^[()[\]"',;:.]+|[()[\]"',;:.]+$/g;
|
|
1306
|
+
const allowed = /^[\p{Nd}+\-\u2010\u2011\u2013\u2014\u2212\u00b1.,:/'\u00a0\u202f$\u20ac\u00a3\u00a5%\u2030\u00d7\u00b0\p{L}]+$/u;
|
|
1307
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
1308
|
+
const range = document.createRange();
|
|
1309
|
+
const seen = new Set();
|
|
1310
|
+
for (let n = walker.nextNode(); n && out.length < 12; n = walker.nextNode()) {
|
|
1311
|
+
const text = n.nodeValue || '';
|
|
1312
|
+
if (!/\d/.test(text)) continue;
|
|
1313
|
+
const el = n.parentElement;
|
|
1314
|
+
if (!el || el.closest(skip) || !shown(el)) continue;
|
|
1315
|
+
for (const m of text.matchAll(runRe)) {
|
|
1316
|
+
const run = m[0];
|
|
1317
|
+
if (!/\d/.test(run)) continue;
|
|
1318
|
+
const lead = run.length - run.replace(/^[()[\]"',;:.]+/, '').length;
|
|
1319
|
+
const value = run.replace(strip, '');
|
|
1320
|
+
if (value.length < 2 || value.length > 16 || !/\d/.test(value) || !allowed.test(value)) continue;
|
|
1321
|
+
if ((value.match(/\p{L}/gu) || []).length > 4) continue;
|
|
1322
|
+
const start = m.index + lead;
|
|
1323
|
+
// a break right after a hyphen, a dash or a slash is a normal line break; the segments between them must stay whole.
|
|
1324
|
+
// A leading sign belongs to the first segment (a "-" apart from "3,2" is a broken value).
|
|
1325
|
+
const cuts = [0];
|
|
1326
|
+
for (let i = 1; i < value.length - 1; i += 1) if (BREAK_AFTER.includes(value[i])) cuts.push(i + 1);
|
|
1327
|
+
cuts.push(value.length);
|
|
1328
|
+
let worst = 1;
|
|
1329
|
+
for (let k = 0; k + 1 < cuts.length; k += 1) {
|
|
1330
|
+
if (cuts[k + 1] - cuts[k] < 2) continue;
|
|
1331
|
+
range.setStart(n, start + cuts[k]);
|
|
1332
|
+
range.setEnd(n, start + cuts[k + 1]);
|
|
1333
|
+
const tops = [];
|
|
1334
|
+
for (const r of range.getClientRects()) {
|
|
1335
|
+
if (r.width <= 0.5) continue;
|
|
1336
|
+
if (!tops.some((t) => Math.abs(t - r.top) <= 2)) tops.push(r.top);
|
|
1337
|
+
}
|
|
1338
|
+
worst = Math.max(worst, tops.length);
|
|
1339
|
+
}
|
|
1340
|
+
if (worst > 1) {
|
|
1341
|
+
const msg = `"${value}" breaks across ${worst} lines (${describe(el)})`;
|
|
1342
|
+
if (!seen.has(msg)) {
|
|
1343
|
+
seen.add(msg);
|
|
1344
|
+
out.push(msg);
|
|
1345
|
+
if (out.length >= 12) break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return out;
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1260
1353
|
/** The scroll container of an element: the nearest ancestor that scrolls vertically, or the document. */
|
|
1261
1354
|
const scroller = (el) => {
|
|
1262
1355
|
for (let a = el.parentElement; a && a !== document.body; a = a.parentElement) {
|
package/gate/run.mjs
CHANGED
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
// Space, its hidden face out of the accessibility tree; a carousel reaches every card, never scrolls sideways
|
|
28
28
|
// G17 animated drawings an animated SVG has its reduced-motion block, plays for 10 s at most and is still under reduced motion
|
|
29
29
|
// G18 Focus mode the App's Focus control opens the case's open group in the full viewport and returns to the same case and
|
|
30
|
-
// group (by the exit control and by Escape); no page scroll, the rail without scroll, one tab row
|
|
30
|
+
// group (by the exit control and by Escape); no page scroll, the rail without scroll, one tab row; no stage
|
|
31
|
+
// control under the Focus chrome (label, HUD, actions); no Focus control inside the Focus frame
|
|
32
|
+
// G19 values whole a short value (number, percentage, amount, unit) sits on one line in every state
|
|
31
33
|
// plus the ADR-0071 and ADR-0017 measures (viewport, one-row tabs, rail without scroll, centred prose, captions,
|
|
32
34
|
// references).
|
|
33
35
|
|
|
@@ -464,6 +466,7 @@ export async function runGate(o) {
|
|
|
464
466
|
if (big) for (const c of f.fillStages ?? []) if (c.h < opt.stageFloor) fail('G6', where, `the drawing of ${c.label} keeps ${Math.round(c.h)} px under its note (floor ${opt.stageFloor} px)`);
|
|
465
467
|
for (const m of await page.evaluate(() => window.__caosGate.drawingText())) fail('G10', where, m);
|
|
466
468
|
if (s.lang === 'es') for (const m of await page.evaluate(() => window.__caosGate.decimals())) fail('G11', where, m);
|
|
469
|
+
for (const m of await page.evaluate(() => window.__caosGate.brokenValues())) fail('G19', where, m);
|
|
467
470
|
if (s.w > 760) for (const m of await page.evaluate(() => window.__caosGate.sticky())) fail('G12', where, m);
|
|
468
471
|
if (s.contrast) for (const m of await page.evaluate(() => window.__caosGate.contrast())) fail('G13', where, m);
|
|
469
472
|
if (f.sectionsWithoutRefs) fail('ADR-0017.4', where, `${f.sectionsWithoutRefs} section(s) end without references or a stated reason`);
|
|
@@ -889,7 +892,10 @@ export async function runGate(o) {
|
|
|
889
892
|
if (f.tabRows.some((r) => r > 1)) fail('G18', at, `${label}: a tab row wraps onto ${Math.max(...f.tabRows)} rows`);
|
|
890
893
|
if (big && f.share < opt.instrumentMin) fail('G18', at, `${label}: the stage covers ${(100 * f.share).toFixed(1)}% of the viewport (floor ${(100 * opt.instrumentMin).toFixed(0)}%)`);
|
|
891
894
|
if (f.exitInside === false) fail('G18', at, `${label}: the exit control is outside the viewport`);
|
|
895
|
+
for (const c of f.covered) fail('G18', at, `${label}: ${c.chrome} covers the control "${c.control}" (the stage must clear the Focus chrome)`);
|
|
896
|
+
if (f.enterInside > 0) fail('G18', at, `${label}: the Focus control shows inside Focus mode (it opens the mode the reader is already in)`);
|
|
892
897
|
for (const m of await page.evaluate(() => window.__caosGate.drawingText())) fail('G10', at, m);
|
|
898
|
+
for (const m of await page.evaluate(() => window.__caosGate.brokenValues())) fail('G19', at, m);
|
|
893
899
|
if (s.contrast) for (const m of await page.evaluate(() => window.__caosGate.contrast())) fail('G13', at, m);
|
|
894
900
|
if (index === 0) await saveShot(page, `focus_${slug(s.route)}_${s.w}x${s.h}_${s.theme}_${s.lang}.png`);
|
|
895
901
|
// leave: the exit control for one case, Escape for the next, so both ways are walked
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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/styles.css
CHANGED
|
@@ -672,7 +672,7 @@ svg.lucide { flex: none; }
|
|
|
672
672
|
.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; }
|
|
673
673
|
.caos-focus-label strong { font-size: 0.96rem; line-height: 1.2; }
|
|
674
674
|
.caos-focus-label span { font-size: 0.78rem; line-height: 1.4; color: var(--color-fg-subtle); }
|
|
675
|
-
.caos-focus-hud { position: absolute; left: 14px; top:
|
|
675
|
+
.caos-focus-hud { position: absolute; left: 14px; top: calc(var(--caos-focus-label-bottom, 96px) + 8px); display: grid; gap: 6px; pointer-events: none; }
|
|
676
676
|
.caos-focus-hud > div { display: grid; gap: 1px; padding: 7px 10px; min-width: 120px; }
|
|
677
677
|
.caos-focus-hud strong { font: 700 1rem var(--font-mono,monospace); }
|
|
678
678
|
.caos-focus-hud span { color: var(--color-fg-subtle); font-size: 0.61rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; }
|
|
@@ -825,11 +825,15 @@ svg.lucide { flex: none; }
|
|
|
825
825
|
|
|
826
826
|
/* == Focus mode for the App (shell 0.14.0, ADR-0070) ================================================================== */
|
|
827
827
|
|
|
828
|
-
/* The stage is the open group; the label overlay (heading and line) sits above it, so the group starts below the label.
|
|
829
|
-
|
|
828
|
+
/* The stage is the open group; the label overlay (heading and line) sits above it, so the group starts below the label.
|
|
829
|
+
FocusShell measures the overlay chrome and sets, on .caos-focus-stage, three lengths the instrument inherits (shell 0.14.3):
|
|
830
|
+
--caos-focus-clear-top (stage top to the lowest chrome box in the upper half, plus 8px), --caos-focus-clear-bottom (stage
|
|
831
|
+
bottom to the highest chrome box in the lower half, plus 8px; unset when none) and --caos-focus-label-bottom (the label's
|
|
832
|
+
bottom edge). The fallbacks below apply before the first measurement and in a non-browser render. */
|
|
833
|
+
.caos-focus-instrument > .caos-cw-focus { position: absolute; inset: 0; padding: var(--caos-focus-clear-top, 4.6rem) 0.9rem max(0.9rem, var(--caos-focus-clear-bottom, 0px)); }
|
|
830
834
|
.caos-focus-instrument > .caos-cw-focus > .tabs { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
|
|
831
835
|
.caos-focus-rail-body { display: flex; flex-direction: column; gap: var(--space-3); padding: 0.85rem; min-width: 0; }
|
|
832
836
|
.caos-focus-enter { align-self: flex-start; }
|
|
833
837
|
@media (max-width: 900px) {
|
|
834
|
-
.caos-focus-instrument > .caos-cw-focus { padding: 6.6rem 0.5rem 0.5rem; }
|
|
838
|
+
.caos-focus-instrument > .caos-cw-focus { padding: var(--caos-focus-clear-top, 6.6rem) 0.5rem max(0.5rem, var(--caos-focus-clear-bottom, 0px)); }
|
|
835
839
|
}
|