@fasl-work/caos-app-shell 0.19.9 → 0.19.10
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 +7 -0
- package/VERSION +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -3
- package/gate/inpage.mjs +55 -6
- package/package.json +1 -1
- package/reserved-classes.json +2 -0
- package/styles.css +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.19.010] - 2026-10-31
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- `SurfacePage document` gives a documentation surface a full-column heading and lede, with one root-owned inset and the existing tabs and Focus controls. OreFlow's Introduction needed document framing without a nested page wrapper or product overrides (issue #167, SHD-088). Products adopt the optional `document` prop and remove their document-width workaround; ordinary surfaces retain their current layout.
|
|
6
|
+
- Executive illustration checks measure transformed text bounds in the outer SVG viewBox and rendered font size through the complete text-to-screen matrix. Translated, scaled, rotated and nested labels are checked in their actual coordinate space; unavailable geometry fails explicitly. The existing bounds tolerance and 9 px font floor are unchanged (issue #167, SHG-076 to SHG-078).
|
|
7
|
+
|
|
1
8
|
## [0.19.009] - 2026-10-30
|
|
2
9
|
|
|
3
10
|
### Fixed
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.19.
|
|
1
|
+
0.19.010
|
package/dist/index.d.ts
CHANGED
|
@@ -888,6 +888,8 @@ interface CardDeckProps {
|
|
|
888
888
|
declare function CardDeck({ cards, flip, carouselAbove, ariaLabel, className, lang: forced }: CardDeckProps): react.JSX.Element;
|
|
889
889
|
|
|
890
890
|
interface SurfacePageProps {
|
|
891
|
+
/** Full-column document framing with the same tabs and Focus behavior. The root owns page padding. */
|
|
892
|
+
document?: boolean;
|
|
891
893
|
/** The route's heading; omit it when the header's active route already names the surface. */
|
|
892
894
|
title?: BiText;
|
|
893
895
|
/** One line under the title: what the surface is for. */
|
|
@@ -925,7 +927,7 @@ interface SurfacePageProps {
|
|
|
925
927
|
* `CaseWorkbench`'s, one piece of code (`useFocusMode`). The control sits at the end of the tab row (a slot beside the tab list) with
|
|
926
928
|
* its space reserved, so it adds no row and moves nothing; it is hidden while the open view has no stage.
|
|
927
929
|
*/
|
|
928
|
-
declare function SurfacePage({ title, lede, actions, tabs, value, onChange, deepLinkView, ariaLabel, instrument, focus, children }: SurfacePageProps): react.JSX.Element;
|
|
930
|
+
declare function SurfacePage({ document: documentMode, title, lede, actions, tabs, value, onChange, deepLinkView, ariaLabel, instrument, focus, children }: SurfacePageProps): react.JSX.Element;
|
|
929
931
|
|
|
930
932
|
/** Writes one query parameter with replaceState, keeping the router's history state (React Router keeps its entry
|
|
931
933
|
* key and index there; replacing it with null broke the index the back button relies on). */
|
package/dist/index.js
CHANGED
|
@@ -3052,7 +3052,7 @@ function textOf(node) {
|
|
|
3052
3052
|
return "";
|
|
3053
3053
|
}
|
|
3054
3054
|
var viewFocus = (tab) => !tab || !tab.focus ? null : tab.focus === true ? {} : tab.focus;
|
|
3055
|
-
function SurfacePage({ title, lede, actions, tabs, value, onChange, deepLinkView = true, ariaLabel, instrument, focus, children }) {
|
|
3055
|
+
function SurfacePage({ document: documentMode = false, title, lede, actions, tabs, value, onChange, deepLinkView = true, ariaLabel, instrument, focus, children }) {
|
|
3056
3056
|
const lang = useShellLang();
|
|
3057
3057
|
const placeAbove = useContext(PanelPathContext);
|
|
3058
3058
|
const ids = (tabs ?? []).map((t2) => t2.id);
|
|
@@ -3110,11 +3110,11 @@ function SurfacePage({ title, lede, actions, tabs, value, onChange, deepLinkView
|
|
|
3110
3110
|
) });
|
|
3111
3111
|
}
|
|
3112
3112
|
const head = title !== void 0 || lede !== void 0 || actions !== void 0 && actions !== null;
|
|
3113
|
-
const page = /* @__PURE__ */ jsxs("div", { className:
|
|
3113
|
+
const page = /* @__PURE__ */ jsxs("div", { className: `page-body wide caos-surface${documentMode ? " caos-surface-doc" : ""}`, "data-doc-page": documentMode ? "" : void 0, "data-surface": "", "data-view-param": reserve ? viewParam ?? "" : void 0, children: [
|
|
3114
3114
|
head && /* @__PURE__ */ jsxs("div", { className: "caos-surface-head", children: [
|
|
3115
3115
|
/* @__PURE__ */ jsxs("div", { className: "caos-surface-heading", children: [
|
|
3116
3116
|
title !== void 0 && /* @__PURE__ */ jsx("h1", { className: "caos-surface-title", children: pick(title, lang) }),
|
|
3117
|
-
lede !== void 0 && /* @__PURE__ */ jsx("p", { className:
|
|
3117
|
+
lede !== void 0 && /* @__PURE__ */ jsx("p", { className: `caos-surface-lede${documentMode ? " lede" : ""}`, children: lede })
|
|
3118
3118
|
] }),
|
|
3119
3119
|
actions !== void 0 && actions !== null && /* @__PURE__ */ jsx("div", { className: "caos-surface-actions", children: actions })
|
|
3120
3120
|
] }),
|
package/gate/inpage.mjs
CHANGED
|
@@ -9,6 +9,55 @@ export function installLib() {
|
|
|
9
9
|
G.raf = 0;
|
|
10
10
|
// The smallest rendered size of an illustration label (EXEC_LIMITS.minLabelPx of gate/areas.mjs; test/executive.test.ts keeps them equal).
|
|
11
11
|
const EXEC_MIN_LABEL_PX = 9;
|
|
12
|
+
/** Local text bounds mapped into the outer illustration's SVG user space, not screen pixels.
|
|
13
|
+
* All four corners matter for rotation/skew. Null is an explicit unmeasurable bound, never a pass.
|
|
14
|
+
* Screen matrices include nested viewports and root CSS transforms; the root inverse cancels them.
|
|
15
|
+
*/
|
|
16
|
+
G.svgTextBounds = (root, text) => {
|
|
17
|
+
try {
|
|
18
|
+
const rootMatrix = root.getScreenCTM();
|
|
19
|
+
const textMatrix = text.getScreenCTM();
|
|
20
|
+
const valid = (m) => m && ['a', 'b', 'c', 'd', 'e', 'f'].every((k) => Number.isFinite(m[k]))
|
|
21
|
+
&& m.a * m.d - m.b * m.c !== 0;
|
|
22
|
+
if (!valid(rootMatrix) || !valid(textMatrix)) return null;
|
|
23
|
+
const matrix = rootMatrix.inverse().multiply(textMatrix);
|
|
24
|
+
if (!valid(matrix)) return null;
|
|
25
|
+
const box = text.getBBox();
|
|
26
|
+
if (!['x', 'y', 'width', 'height'].every((k) => Number.isFinite(box[k]))) return null;
|
|
27
|
+
const corners = [[box.x, box.y], [box.x + box.width, box.y],
|
|
28
|
+
[box.x, box.y + box.height], [box.x + box.width, box.y + box.height]]
|
|
29
|
+
.map(([x, y]) => ({ x: matrix.a * x + matrix.c * y + matrix.e,
|
|
30
|
+
y: matrix.b * x + matrix.d * y + matrix.f }));
|
|
31
|
+
if (corners.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) return null;
|
|
32
|
+
const x = Math.min(...corners.map((p) => p.x));
|
|
33
|
+
const y = Math.min(...corners.map((p) => p.y));
|
|
34
|
+
return { x, y, width: Math.max(...corners.map((p) => p.x)) - x,
|
|
35
|
+
height: Math.max(...corners.map((p) => p.y)) - y };
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** Conservative rendered font size: the least stretch of the full text-to-screen mapping.
|
|
41
|
+
* Unlike a root bounding-rect ratio, singular values retain group/text/viewport scaling and
|
|
42
|
+
* ignore pure rotation. Normalize first, then use |det| / sigmaMax to avoid subtracting eigenvalues.
|
|
43
|
+
*/
|
|
44
|
+
const svgTextFontPx = (text) => {
|
|
45
|
+
try {
|
|
46
|
+
const m = text.getScreenCTM();
|
|
47
|
+
const font = parseFloat(getComputedStyle(text).fontSize);
|
|
48
|
+
if (!m || m.is2D === false || !['a', 'b', 'c', 'd'].every((k) => Number.isFinite(m[k]))
|
|
49
|
+
|| !Number.isFinite(font) || font <= 0) return null;
|
|
50
|
+
const norm = Math.max(Math.abs(m.a), Math.abs(m.b), Math.abs(m.c), Math.abs(m.d));
|
|
51
|
+
if (norm === 0) return null;
|
|
52
|
+
const a = m.a / norm, b = m.b / norm, c = m.c / norm, d = m.d / norm;
|
|
53
|
+
const largest = (Math.hypot(a + d, b - c) + Math.hypot(a - d, b + c)) / 2;
|
|
54
|
+
const scale = Math.abs(a * d - b * c) / largest * norm;
|
|
55
|
+
const px = font * scale;
|
|
56
|
+
return Number.isFinite(px) && px > 0 ? px : null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
12
61
|
G.counting = false;
|
|
13
62
|
const nativeRaf = window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : null;
|
|
14
63
|
if (nativeRaf) {
|
|
@@ -730,18 +779,18 @@ export function installLib() {
|
|
|
730
779
|
if (!svg) return info;
|
|
731
780
|
info.literal = literalsOf(svg);
|
|
732
781
|
const vb = svg.viewBox && svg.viewBox.baseVal;
|
|
733
|
-
const w = svg.getBoundingClientRect().width;
|
|
734
|
-
const scale = vb && vb.width ? w / vb.width : 1;
|
|
735
782
|
for (const t of svg.querySelectorAll('text')) {
|
|
736
783
|
const label = (t.textContent || '').trim();
|
|
737
784
|
if (!label || !visible(t)) continue;
|
|
738
785
|
info.labels += 1;
|
|
739
786
|
if (vb && vb.width) {
|
|
740
|
-
const b =
|
|
741
|
-
if (b
|
|
787
|
+
const b = G.svgTextBounds(svg, t);
|
|
788
|
+
if (!b) info.outside.push(`${label.slice(0, 40)} (bounds unavailable)`);
|
|
789
|
+
else if (b.x < vb.x - 1 || b.y < vb.y - 1 || b.x + b.width > vb.x + vb.width + 1 || b.y + b.height > vb.y + vb.height + 1) info.outside.push(label.slice(0, 40));
|
|
742
790
|
}
|
|
743
|
-
const px =
|
|
744
|
-
if (px
|
|
791
|
+
const px = svgTextFontPx(t);
|
|
792
|
+
if (px === null) info.small.push(`"${label.slice(0, 30)}" (font scale unavailable)`);
|
|
793
|
+
else if (px < EXEC_MIN_LABEL_PX) info.small.push(`"${label.slice(0, 30)}" at ${px.toFixed(1)}px`);
|
|
745
794
|
}
|
|
746
795
|
return info;
|
|
747
796
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasl-work/caos-app-shell",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.10",
|
|
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
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
"caos-surface",
|
|
168
168
|
"caos-surface-actions",
|
|
169
169
|
"caos-surface-body",
|
|
170
|
+
"caos-surface-doc",
|
|
170
171
|
"caos-surface-focus",
|
|
171
172
|
"caos-surface-head",
|
|
172
173
|
"caos-surface-heading",
|
|
@@ -494,6 +495,7 @@
|
|
|
494
495
|
"caos-surface",
|
|
495
496
|
"caos-surface-actions",
|
|
496
497
|
"caos-surface-body",
|
|
498
|
+
"caos-surface-doc",
|
|
497
499
|
"caos-surface-focus",
|
|
498
500
|
"caos-surface-head",
|
|
499
501
|
"caos-surface-heading",
|
package/styles.css
CHANGED
|
@@ -252,6 +252,9 @@ code, kbd { font-family: var(--font-mono); }
|
|
|
252
252
|
.caos-surface-title { margin: 0; font-size: var(--text-xl); }
|
|
253
253
|
.caos-surface-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
|
|
254
254
|
.caos-surface-lede { margin: 0; color: var(--color-fg-subtle); font-size: var(--text-md); max-width: var(--measure-text); }
|
|
255
|
+
/* Document surfaces own their one page inset; actions cannot shrink the title/lede column. */
|
|
256
|
+
.caos-surface-doc > .caos-surface-head > .caos-surface-heading { flex: 1 1 100%; width: 100%; }
|
|
257
|
+
.caos-surface-doc .caos-surface-lede { max-width: none; font-size: var(--text-md); }
|
|
255
258
|
.caos-surface-body { flex: 1 1 auto; min-height: 0; min-width: 0; display: flex; flex-direction: column; }
|
|
256
259
|
.caos-surface-body > .tabs { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
|
257
260
|
.caos-surface-body > .tabs > .tabpanel:not([hidden]) { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 0.6rem; }
|