@starci/hfs 4.7.47 → 4.9.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.
Files changed (153) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -9
  3. package/emit/operation-schemas.mjs +3 -0
  4. package/lint/bound-sys.cjs +2 -2
  5. package/package.json +11 -5
  6. package/runtime/engine/admission.mjs +1 -1
  7. package/runtime/engine/config.mjs +2 -2
  8. package/runtime/engine/db/machine.mjs +2 -3
  9. package/runtime/engine/invalid-config.mjs +1 -1
  10. package/runtime/knowledge/grammars/{starci → core}/playbook.yaml +3 -3
  11. package/runtime/knowledge/grammars/index.yaml +14 -23
  12. package/runtime/knowledge/grammars/offset-pop/playbook.yaml +2 -2
  13. package/runtime/knowledge/hfs/canon-pins.yaml +23 -23
  14. package/runtime/knowledge/hfs/fe-stack.yaml +1 -1
  15. package/runtime/knowledge/hfs/rules.yaml +83 -60
  16. package/runtime/modules/kernel/failure-codes.yaml +23 -13
  17. package/runtime/scripts/guards/git-policy.mjs +2 -3
  18. package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
  19. package/runtime/scripts/hfs/rules/pipeline.mjs +5 -5
  20. package/runtime/scripts/lib/example-refs.mjs +10 -3
  21. package/runtime/scripts/lib/grammar-lint.mjs +16 -3
  22. package/runtime/scripts/lib/package-at.mjs +5 -6
  23. package/runtime/scripts/lib/roots.mjs +2 -2
  24. package/runtime/scripts/work/draw/draw-dna-findings.mjs +23 -12
  25. package/runtime/scripts/work/draw/draw-dna.mjs +14 -8
  26. package/scaffold/app.mjs +6 -6
  27. package/src/main.mjs +3 -3
  28. package/sync/hygiene.mjs +1 -1
  29. package/sync/index.mjs +15 -2
  30. package/sync/managed.mjs +3 -2
  31. package/templates/app/codegen-graphql.mjs.partial +4 -4
  32. package/templates/app/quality-config/sonar-project.properties +4 -2
  33. package/runtime/engine/canonical-json.mjs +0 -14
  34. package/runtime/engine/db/ledger-open.mjs +0 -38
  35. package/runtime/engine/db/ledger-paths.mjs +0 -87
  36. package/runtime/engine/db/ledger.mjs +0 -1068
  37. package/runtime/engine/db/provision-authority.mjs +0 -76
  38. package/runtime/engine/db/provision-operations.mjs +0 -146
  39. package/runtime/packages/grammar/scripts/build-stamp.mjs +0 -98
  40. package/runtime/scripts/agent/codex-app-server.mjs +0 -79
  41. package/runtime/scripts/agent/codex-mirror-source.mjs +0 -34
  42. package/runtime/scripts/agent/host-agents.mjs +0 -80
  43. package/runtime/scripts/agent/launch-trust-policy.mjs +0 -53
  44. package/runtime/scripts/agent/trust-launch.mjs +0 -191
  45. package/runtime/scripts/agent/trust.mjs +0 -523
  46. package/runtime/scripts/api/fs/temp-path.mjs +0 -8
  47. package/runtime/scripts/api/git/diff-names.mjs +0 -8
  48. package/runtime/scripts/api/git/head-time.mjs +0 -10
  49. package/runtime/scripts/api/git/index-lock.mjs +0 -78
  50. package/runtime/scripts/api/git/log.mjs +0 -5
  51. package/runtime/scripts/api/git/merge-tree.mjs +0 -5
  52. package/runtime/scripts/api/git/rev-list.mjs +0 -5
  53. package/runtime/scripts/api/git/rev-parse.mjs +0 -13
  54. package/runtime/scripts/api/git/status-query.mjs +0 -5
  55. package/runtime/scripts/api/git/worktree-list-query.mjs +0 -5
  56. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  57. package/runtime/scripts/api/npm/dist-tag.mjs +0 -8
  58. package/runtime/scripts/api/npm/lib.mjs +0 -32
  59. package/runtime/scripts/api/npm/pack-dry-run.mjs +0 -9
  60. package/runtime/scripts/api/npm/run-npm.mjs +0 -6
  61. package/runtime/scripts/api/npm/view.mjs +0 -30
  62. package/runtime/scripts/api/npm/whoami.mjs +0 -8
  63. package/runtime/scripts/api/process/hide-child-windows.mjs +0 -31
  64. package/runtime/scripts/api/process/kill-tree.mjs +0 -22
  65. package/runtime/scripts/api/process/lib.mjs +0 -125
  66. package/runtime/scripts/api/process/probe-guard-command.mjs +0 -74
  67. package/runtime/scripts/api/process/process-list.mjs +0 -28
  68. package/runtime/scripts/api/process/system-tool.mjs +0 -37
  69. package/runtime/scripts/gates/canon-digest.mjs +0 -81
  70. package/runtime/scripts/gates/canon-pins.mjs +0 -119
  71. package/runtime/scripts/gates/gate-input.mjs +0 -98
  72. package/runtime/scripts/gates/gate.mjs +0 -636
  73. package/runtime/scripts/gates/grammar-dist.mjs +0 -206
  74. package/runtime/scripts/gates/output.mjs +0 -40
  75. package/runtime/scripts/gates/runtime-package-selection.mjs +0 -27
  76. package/runtime/scripts/gates/test-world-run.mjs +0 -252
  77. package/runtime/scripts/gates/type-impact.mjs +0 -277
  78. package/runtime/scripts/gates/type-program.mjs +0 -113
  79. package/runtime/scripts/guards/assigned-command.mjs +0 -12
  80. package/runtime/scripts/guards/call-rights.mjs +0 -47
  81. package/runtime/scripts/guards/command-guard.mjs +0 -468
  82. package/runtime/scripts/guards/command-policy.mjs +0 -342
  83. package/runtime/scripts/guards/critic-reach.mjs +0 -82
  84. package/runtime/scripts/guards/deps-guard.mjs +0 -150
  85. package/runtime/scripts/guards/env-dump-verdict.mjs +0 -89
  86. package/runtime/scripts/guards/guards-root.mjs +0 -27
  87. package/runtime/scripts/guards/hook-install.mjs +0 -343
  88. package/runtime/scripts/guards/hook-verdict.mjs +0 -12
  89. package/runtime/scripts/guards/install-verdict.mjs +0 -41
  90. package/runtime/scripts/guards/kernel-seat.mjs +0 -63
  91. package/runtime/scripts/guards/launch-verdict.mjs +0 -74
  92. package/runtime/scripts/guards/op-starci.mjs +0 -16
  93. package/runtime/scripts/guards/op-write-scope.mjs +0 -27
  94. package/runtime/scripts/guards/orca-self-lifecycle.mjs +0 -39
  95. package/runtime/scripts/guards/process-kill-verdict.mjs +0 -106
  96. package/runtime/scripts/guards/protected-zone.mjs +0 -56
  97. package/runtime/scripts/guards/refusals.mjs +0 -27
  98. package/runtime/scripts/guards/rights.mjs +0 -268
  99. package/runtime/scripts/guards/seat-tools.mjs +0 -47
  100. package/runtime/scripts/guards/shell-commands.mjs +0 -328
  101. package/runtime/scripts/guards/supervisor-seat.mjs +0 -80
  102. package/runtime/scripts/guards/verdict-process.mjs +0 -81
  103. package/runtime/scripts/lib/app-data.mjs +0 -11
  104. package/runtime/scripts/lib/canon-binding-text.mjs +0 -129
  105. package/runtime/scripts/lib/check-format.mjs +0 -7
  106. package/runtime/scripts/lib/failure-code-reads.mjs +0 -100
  107. package/runtime/scripts/lib/failure-code-template-errors.mjs +0 -161
  108. package/runtime/scripts/lib/git-dir.mjs +0 -120
  109. package/runtime/scripts/lib/guard-command.mjs +0 -24
  110. package/runtime/scripts/lib/interrupt-cleanup.mjs +0 -38
  111. package/runtime/scripts/lib/json-formats.mjs +0 -34
  112. package/runtime/scripts/lib/managed-json.mjs +0 -51
  113. package/runtime/scripts/lib/menu-parts.mjs +0 -11
  114. package/runtime/scripts/lib/op-gate.mjs +0 -15
  115. package/runtime/scripts/lib/provision-control-failure.mjs +0 -32
  116. package/runtime/scripts/lib/release-version.mjs +0 -134
  117. package/runtime/scripts/lib/runtime-location.mjs +0 -21
  118. package/runtime/scripts/lib/runtime-package-inventory.mjs +0 -21
  119. package/runtime/scripts/lib/runtime-package-manifests.mjs +0 -84
  120. package/runtime/scripts/lib/runtime-publication-tags.mjs +0 -56
  121. package/runtime/scripts/lib/seat-env.mjs +0 -43
  122. package/runtime/scripts/lib/sonar-new-code.mjs +0 -58
  123. package/runtime/scripts/lib/source-package-imports.mjs +0 -111
  124. package/runtime/scripts/lib/tar-files.mjs +0 -39
  125. package/runtime/scripts/lib/uat-run-folder.mjs +0 -118
  126. package/runtime/scripts/lib/worktree-record.mjs +0 -14
  127. package/runtime/scripts/machine/ask-receipts.mjs +0 -113
  128. package/runtime/scripts/machine/evidence-store.mjs +0 -223
  129. package/runtime/scripts/machine/home.mjs +0 -216
  130. package/runtime/scripts/machine/host-lock.mjs +0 -184
  131. package/runtime/scripts/machine/lock-recovery.mjs +0 -55
  132. package/runtime/scripts/machine/roles-contract.mjs +0 -90
  133. package/runtime/scripts/machine/runtime-change.mjs +0 -20
  134. package/runtime/scripts/machine/workflow-tree.mjs +0 -122
  135. package/runtime/scripts/reconciler/checklist-items.mjs +0 -7
  136. package/runtime/scripts/reconciler/guard-row.mjs +0 -25
  137. package/runtime/scripts/work/brand/brand-colour.mjs +0 -125
  138. package/runtime/scripts/work/brand/brand-tokens.mjs +0 -167
  139. package/runtime/scripts/work/brand/brand-value-source.mjs +0 -106
  140. package/runtime/scripts/work/brand/brand.mjs +0 -892
  141. package/runtime/scripts/work/draw/draw-layer.mjs +0 -306
  142. package/runtime/scripts/work/draw/draw-rationale-page.mjs +0 -237
  143. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +0 -14
  144. package/runtime/scripts/work/draw/draw-rationale.mjs +0 -353
  145. package/runtime/scripts/work/draw/draw-source.mjs +0 -412
  146. package/runtime/scripts/work/draw/draw-taste.mjs +0 -278
  147. package/runtime/scripts/work/draw/draw-typecheck.mjs +0 -72
  148. package/runtime/scripts/work/draw/rationale-findings.mjs +0 -205
  149. package/runtime/scripts/work/draw-grammar.mjs +0 -175
  150. package/runtime/scripts/work/draw-render-page.mjs +0 -188
  151. package/runtime/scripts/work/draw-render.mjs +0 -626
  152. package/runtime/scripts/work/png.mjs +0 -237
  153. package/runtime/scripts/work/render-tools.mjs +0 -64
@@ -1,175 +0,0 @@
1
- #!/usr/bin/env node
2
- // draw-grammar.mjs — which @starci/grammar a drawing renders against (owner ruling 2026-09-27: the draw must render
3
- // against the grammar version the product will SHIP).
4
- //
5
- // Candidates, in order:
6
- // product the grammar the product has installed (resolved from the product app dir, as its bundler would)
7
- // claude-dist the runtime-built package (.claude/packages/grammar/dist, the grammar main carries - possibly
8
- // newer than the product's install, e.g. Meter segments 0.5.2 against an installed 0.5.0)
9
- // The product's install wins when it SATISFIES what the draw needs, and "satisfies" is decided by TypeScript: the
10
- // draw file type-checks against that candidate's types (scripts/work/draw/draw-source.mjs typecheckDraw). When only
11
- // claude-dist satisfies, the drawing renders against it through an alias in the draw bundle ONLY (draw-render.mjs
12
- // --grammar-root), the provenance says so (grammarSource claude-dist@<v>) and an upgrade of the product is OWED
13
- // (upgradeOwed {status: owed, from, to, range, inRange}) - interface.implement bumps the product's dependency; the
14
- // draw never touches the product's package.json. When neither satisfies: DRAW_TYPECHECK_FAILED.
15
- //
16
- // starci work draw-grammar --product <app dir> [--file <X.draw.tsx>] [--grammar auto|product|claude-dist]
17
- // [--grammar-dist <package root>] (default .claude/packages/grammar; env STARCI_GRAMMAR_DIST; a lane checkout
18
- // without a built dist points it at the live checkout's)
19
- import path from 'node:path';
20
- import { fileURLToPath } from 'node:url';
21
- import { revParseQuery } from '../api/git/rev-parse-query.mjs';
22
- import { findPackage } from '../lib/package-at.mjs';
23
- import { readJsonFile } from '../lib/json.mjs';
24
- import { GRAMMAR_PACKAGE, typecheckDraw } from './draw/draw-typecheck.mjs';
25
- import { grammarDistStatus } from '../gates/grammar-dist.mjs';
26
- import { isFile } from './work-io.mjs';
27
- import { isMain } from '../lib/is-main.mjs';
28
-
29
- const GRAMMAR_SOURCES = Object.freeze(['product', 'claude-dist']);
30
- export const PREFERENCES = Object.freeze(['auto', ...GRAMMAR_SOURCES]);
31
- const SKILL_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
32
-
33
- /** The product's declared range for the grammar (dependencies/devDependencies/peerDependencies), or null. */
34
- function productRangeOf(productDir) {
35
- const pkg = readJsonFile(path.join(productDir, 'package.json'));
36
- return pkg?.dependencies?.[GRAMMAR_PACKAGE] ?? pkg?.devDependencies?.[GRAMMAR_PACKAGE] ?? pkg?.peerDependencies?.[GRAMMAR_PACKAGE] ?? null;
37
- }
38
-
39
- /** A built grammar package root: its package.json plus dist/<family>/index.d.ts and .js. */
40
- const builtGrammar = (root) => isFile(path.join(root, 'package.json')) && isFile(path.join(root, 'dist', 'core', 'index.d.ts')) && isFile(path.join(root, 'dist', 'core', 'index.js'));
41
-
42
- /**
43
- * The main worktree of the git checkout `dir` lives in (a lane worktree's live runtime), or null: a lane checkout never
44
- * builds packages/grammar/dist (dist/ is untracked), so its drawings resolve the live runtime's build.
45
- */
46
- function mainWorktreeOf(dir) {
47
- try {
48
- const r = revParseQuery(['--path-format=absolute', '--git-common-dir'], { dir, timeout: 10_000 });
49
- const common = r.status === 0 ? r.stdout.trim() : '';
50
- if (!common || path.basename(common) !== '.git') return null;
51
- const main = path.dirname(common);
52
- return path.resolve(main) === path.resolve(dir) ? null : main;
53
- } catch { return null; }
54
- }
55
-
56
- /**
57
- * The claude-dist roots, best first: --grammar-dist / STARCI_GRAMMAR_DIST when given (used as is), else the runtime's
58
- * packages/grammar when its build is FRESH (grammar-dist.mjs: stamped from the current source), else the live main
59
- * worktree's fresh build (a lane checkout), else the runtime's built but stale dist - named stale, never silently.
60
- */
61
- function claudeDistRoots({ skillRoot = SKILL_ROOT, grammarDist = null, env = process.env, status = grammarDistStatus, mainOf = mainWorktreeOf } = {}) {
62
- const pinned = grammarDist ?? env.STARCI_GRAMMAR_DIST ?? null;
63
- if (pinned) return [{ root: path.resolve(pinned), status: null, via: grammarDist ? '--grammar-dist' : 'STARCI_GRAMMAR_DIST' }];
64
- const own = path.join(skillRoot, 'packages', 'grammar');
65
- const main = mainOf(skillRoot);
66
- const tried = [{ root: own, via: 'runtime' }, ...(main ? [{ root: path.join(main, 'packages', 'grammar'), via: 'main-worktree' }] : [])]
67
- .filter((c) => builtGrammar(c.root)).map((c) => ({ ...c, status: status(c.root) }));
68
- const fresh = tried.filter((c) => c.status?.ok);
69
- return fresh.length ? [fresh[0]] : tried.slice(0, 1);
70
- }
71
-
72
- function grammarCandidates({ productDir, skillRoot = SKILL_ROOT, grammarDist = null, claudeRoots = null }) {
73
- const out = [];
74
- const installed = productDir ? findPackage([productDir], [GRAMMAR_PACKAGE]) : null;
75
- if (installed && builtGrammar(installed.root)) out.push({ source: 'product', root: installed.root, version: installed.version });
76
- for (const c of claudeRoots ?? claudeDistRoots({ skillRoot, grammarDist })) {
77
- if (!builtGrammar(c.root)) continue;
78
- out.push({ source: 'claude-dist', root: c.root, version: readJsonFile(path.join(c.root, 'package.json'))?.version ?? null, via: c.via,
79
- ...(c.status ? { dist: { state: c.status.state, ok: c.status.ok, detail: c.status.detail } } : {}) });
80
- }
81
- return out;
82
- }
83
-
84
- const parse = (v) => { const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v ?? '').trim()); return m ? m.slice(1, 4).map(Number) : null; };
85
- const cmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
86
- /** Minimal semver range test: exact, ^, ~, >=, * (enough for a dependency line); null when unreadable. */
87
- function satisfiesRange(version, range) {
88
- const v = parse(version);
89
- if (!v || range == null) return null;
90
- const r = String(range).trim();
91
- if (r === '*' || r === 'latest' || r === '') return true;
92
- const m = /^(\^|~|>=|=)?\s*v?(\d+\.\d+\.\d+)/.exec(r);
93
- if (!m) return null;
94
- const base = parse(m[2]);
95
- if (cmp(v, base) < 0) return false;
96
- if (m[1] === '>=') return true;
97
- if (!m[1] || m[1] === '=') return cmp(v, base) === 0;
98
- if (m[1] === '~') return v[0] === base[0] && v[1] === base[1];
99
- // caret: the left-most non-zero part is fixed
100
- if (base[0] > 0) return v[0] === base[0];
101
- if (base[1] > 0) return v[0] === 0 && v[1] === base[1];
102
- return v[0] === 0 && v[1] === 0 && v[2] === base[2];
103
- }
104
-
105
- function tryGrammarCandidates(candidates, { file, productDir, typecheck }) {
106
- const attempts = [];
107
- let pick = null;
108
- for (const c of candidates) {
109
- if (!file) { pick = c; break; }
110
- const r = typecheck({ file, productDir, grammarRoot: c.root });
111
- attempts.push({ source: c.source, version: c.version, root: c.root, ok: r.ok, errors: r.errors.slice(0, 20), typescript: r.typescript ?? null, ...(c.dist ? { dist: c.dist } : {}) });
112
- if (r.ok) { pick = c; break; }
113
- }
114
- return { attempts, pick };
115
- }
116
-
117
- function upgradeOwedFor(pick, product, productRange) {
118
- return pick?.source === 'claude-dist' ? { status: 'owed', package: GRAMMAR_PACKAGE, from: product?.version ?? null, to: pick.version, range: productRange,
119
- inRange: satisfiesRange(pick.version, productRange), why: product ? `the product's installed ${GRAMMAR_PACKAGE}@${product.version} does not satisfy the drawing (it fails to type-check); ${pick.version} does` : `the product has no built ${GRAMMAR_PACKAGE} install` } : null;
120
- }
121
-
122
- function missingGrammarError(candidates, skillRoot) {
123
- return candidates.length
124
- ? `no grammar candidate type-checks the drawing (${candidates.map((candidate) => candidate.source + '@' + candidate.version + (candidate.dist && !candidate.dist.ok ? ' - its dist is ' + candidate.dist.state + ': ' + candidate.dist.detail + '; run npm run build in packages/grammar' : '')).join(', ')})`
125
- : `no built ${GRAMMAR_PACKAGE} (product install, ${path.join(skillRoot, 'packages', 'grammar', 'dist')} or the main worktree's; run npm run build in packages/grammar, or pass --grammar-dist)`;
126
- }
127
-
128
- /**
129
- * Resolve the grammar a draw file renders against. `typecheck` is injectable (tests). Without `file` the first
130
- * candidate of the preference wins untested. Returns {ok, pick, grammarSource, productVersion, productRange,
131
- * upgradeOwed, attempts:[{source, version, root, ok, errors}], error?}.
132
- */
133
- export function resolveDrawGrammar({ file = null, productDir, skillRoot = SKILL_ROOT, grammarDist = null, prefer = 'auto', typecheck = typecheckDraw, claudeRoots = null }) {
134
- if (!PREFERENCES.includes(prefer)) throw new Error(`--grammar must be one of ${PREFERENCES.join('|')}`);
135
- const all = grammarCandidates({ productDir, skillRoot, grammarDist, claudeRoots });
136
- const product = all.find((c) => c.source === 'product') ?? null;
137
- const productRange = productRangeOf(productDir);
138
- const candidates = prefer === 'auto' ? all : all.filter((c) => c.source === prefer);
139
- const { attempts, pick } = tryGrammarCandidates(candidates, { file, productDir, typecheck });
140
- const upgradeOwed = upgradeOwedFor(pick, product, productRange);
141
- return {
142
- ok: Boolean(pick), pick, grammarSource: pick ? `${pick.source}@${pick.version}` : null, productVersion: product?.version ?? null, productRange, upgradeOwed, attempts,
143
- ...(pick ? {} : { error: missingGrammarError(candidates, skillRoot) }),
144
- };
145
- }
146
-
147
- /** The file a grammar subpath import resolves to inside a grammar root (package exports; `import` for JS). */
148
- export function grammarEntry(root, subpath = '') {
149
- const pkg = readJsonFile(path.join(root, 'package.json'));
150
- const key = subpath ? `./${subpath.replace(/^\//, '')}` : '.';
151
- const mapped = pkg?.exports?.[key];
152
- const target = typeof mapped === 'string' ? mapped : mapped?.import ?? mapped?.default ?? mapped?.style ?? null;
153
- if (target) return path.join(root, target);
154
- if (!subpath) return path.join(root, 'dist', 'core', 'index.js');
155
- return path.join(root, subpath);
156
- }
157
-
158
- async function main(argv) {
159
- const val = (k) => { const i = argv.indexOf(k); return i >= 0 ? argv[i + 1] : null; };
160
- const productDir = val('--product');
161
- if (!productDir) { process.stderr.write('use: starci work draw-grammar --product <app dir> [--file <X.draw.tsx>] [--grammar auto|product|claude-dist] [--json]\n'); return 2; }
162
- const r = resolveDrawGrammar({ file: val('--file') ? path.resolve(val('--file')) : null, productDir: path.resolve(productDir), prefer: val('--grammar') ?? 'auto', grammarDist: val('--grammar-dist') });
163
- if (argv.includes('--json')) process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
164
- else {
165
- const summary = r.ok ? 'grammar ' + r.grammarSource : 'UNRESOLVED: ' + r.error;
166
- const upgrade = r.upgradeOwed ? `; product upgrade owed ${r.upgradeOwed.from} -> ${r.upgradeOwed.to} (range ${r.upgradeOwed.range}, in range ${r.upgradeOwed.inRange})` : '';
167
- const attempts = r.attempts.map((a) => ' ' + a.source + '@' + a.version + ': ' + (a.ok ? 'type-checks' : a.errors.length + ' error(s): ' + a.errors.slice(0, 3).map((e) => e.code + ' ' + e.message).join(' | '))).join('\n');
168
- process.stdout.write(`${summary}${upgrade}\n${attempts}\n`);
169
- }
170
- return r.ok ? 0 : 1;
171
- }
172
-
173
- if (isMain(import.meta.url)) {
174
- try { process.exitCode = await main(process.argv.slice(2)); } catch (e) { process.stderr.write(`draw-grammar: ${e?.stack ?? e}\n`); process.exitCode = 2; }
175
- }
@@ -1,188 +0,0 @@
1
- // draw-render-page.mjs - the in-page half of draw-render.mjs. A page function is serialized to a string, so it cannot
2
- // reach the module scope: measureInPage assembles one script from the source of every function below (each is a
3
- // top-level declaration that reads only the page's globals and its own parameters) and runs it in the page.
4
-
5
- function measurePage({ generic, exemptSelector, layoutAttr = 'data-draw-layout' }) {
6
- const faces = [...document.fonts].map((face) => ({ family: face.family.replace(/^(["'])(.*)\1$/, '$2'), weight: face.weight, style: face.style, status: face.status }));
7
- const fontData = measureFonts(generic);
8
- const de = document.documentElement;
9
- const pageWidth = de.clientWidth;
10
- const scrollWidth = Math.max(de.scrollWidth, document.body?.scrollWidth ?? 0);
11
- const overflowing = measureOverflow(pageWidth, scrollWidth);
12
- const root = document.getElementById('root');
13
- const accentExempt = measureAccentExempt(exemptSelector);
14
- const artwork = measureArtwork();
15
- const anatomy = measureAnatomy();
16
- const { ownership, dom } = measureOwnership(root, layoutAttr);
17
- return { ownership, dom, anatomy, accentExempt, artwork, faces, stacks: [...fontData.stacks], local: fontData.local, pageWidth, innerWidth, scrollWidth, overflowing, documentHeight: de.scrollHeight,
18
- rendered: document.documentElement.dataset.drawHarness === 'component' ? Boolean(root && root.childElementCount) : null };
19
- }
20
-
21
- function visible(el) {
22
- const style = getComputedStyle(el);
23
- return style.display !== 'none' && style.visibility !== 'hidden';
24
- }
25
-
26
- function measureFonts(genericFamilies) {
27
- const stacks = new Set();
28
- const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_TEXT);
29
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
30
- const el = node.parentElement;
31
- if (el && node.textContent.trim() && !['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE'].includes(el.tagName) && visible(el)) { stacks.add(getComputedStyle(el).fontFamily); }
32
- }
33
- for (const el of document.querySelectorAll('input,textarea,select,button')) {
34
- if (visible(el)) { stacks.add(getComputedStyle(el).fontFamily); }
35
- }
36
- const ctx = document.createElement('canvas').getContext('2d');
37
- const sample = 'mmmmmmmmmmlli10WQ@#';
38
- const width = (font) => { ctx.font = `72px ${font}`; return ctx.measureText(sample).width; };
39
- const families = new Set([...stacks].flatMap((stack) => stack.split(',').map((family) => family.trim().replace(/^(["'])(.*)\1$/, '$2'))).filter((family) => family && !genericFamilies.includes(family.toLowerCase())));
40
- const local = [...families].filter((family) => ['monospace', 'serif', 'sans-serif'].some((base) => width(`"${family}", ${base}`) !== width(base)));
41
- return { stacks, local };
42
- }
43
-
44
- function measureOverflow(width, scroll) {
45
- if (scroll > width) {
46
- return [...document.querySelectorAll('body *')].filter((el) => el.getBoundingClientRect().right > width + 0.5)
47
- .slice(0, 10).map((el) => el.tagName.toLowerCase() + (el.id ? '#' + el.id : '') + (typeof el.className === 'string' && el.className ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.') : ''));
48
- }
49
- return [];
50
- }
51
-
52
- function measureAccentExempt(exemptSelector) {
53
- try {
54
- return [...document.querySelectorAll(exemptSelector)].map((el) => el.getBoundingClientRect())
55
- .filter((rect) => rect.width > 0 && rect.height > 0).map((rect) => ({ x: rect.left + scrollX, y: rect.top + scrollY, width: rect.width, height: rect.height }));
56
- } catch { return []; }
57
- }
58
-
59
- function measureArtwork() {
60
- try {
61
- return [...document.querySelectorAll('img')].filter((el) => visible(el) && (el.currentSrc || el.src)).map((el) => {
62
- const rect = el.getBoundingClientRect();
63
- let x0 = rect.left, y0 = rect.top, x1 = rect.right, y1 = rect.bottom;
64
- for (let ancestor = el.parentElement; ancestor; ancestor = ancestor.parentElement) {
65
- const style = getComputedStyle(ancestor);
66
- if (style.overflowX !== 'visible' || style.overflowY !== 'visible') {
67
- const clip = ancestor.getBoundingClientRect();
68
- x0 = Math.max(x0, clip.left); y0 = Math.max(y0, clip.top); x1 = Math.min(x1, clip.right); y1 = Math.min(y1, clip.bottom);
69
- }
70
- }
71
- return { src: el.currentSrc || el.src, slot: el.closest('[data-artwork-slot]')?.dataset.artworkSlot ?? null,
72
- x: x0 + scrollX, y: y0 + scrollY, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
73
- }).filter((art) => art.width > 0 && art.height > 0);
74
- } catch { return []; }
75
- }
76
-
77
- function tagOf(el) {
78
- return el.tagName.toLowerCase() + (el.id ? '#' + el.id : '');
79
- }
80
-
81
- function partIn(rootEl, names) {
82
- return [...rootEl.querySelectorAll('[data-grammar-part]')].find((part) => names.includes(part.dataset.grammarPart.trim()));
83
- }
84
-
85
- function measureAnatomy() {
86
- const anatomy = { alerts: [], meters: [] };
87
- try {
88
- const probe = document.createElement('div');
89
- probe.style.cssText = 'position:absolute;visibility:hidden;background-color:var(--surface)';
90
- document.body.appendChild(probe);
91
- const surface = getComputedStyle(probe).backgroundColor;
92
- probe.remove();
93
- for (const el of document.querySelectorAll('[data-grammar-component="Alert"],[data-component="Alert"]')) {
94
- const alert = measureAlert(el, surface);
95
- if (alert) { anatomy.alerts.push(alert); }
96
- }
97
- for (const el of document.querySelectorAll('[data-grammar-component="Meter"],[data-component="Meter"]')) {
98
- const meter = measureMeter(el);
99
- if (meter) { anatomy.meters.push(meter); }
100
- }
101
- } catch { /* the anatomy is advisory measurement; the static gate still runs */ }
102
- return anatomy;
103
- }
104
-
105
- function measureAlert(el, surface) {
106
- if ('grammarPart' in el.dataset) { return null; }
107
- const indicator = partIn(el, ['alert-indicator', 'indicator']) ?? el.querySelector('.alert__indicator');
108
- const title = partIn(el, ['alert-title', 'title']) ?? el.querySelector('.alert__title');
109
- const box = indicator?.getBoundingClientRect();
110
- return { desc: tagOf(el), background: getComputedStyle(el).backgroundColor, surface,
111
- indicator: box ? { width: box.width, height: box.height } : null, tile: Boolean(el.querySelector('[data-grammar-component="IconTile"],[data-component="IconTile"]')),
112
- indicatorColor: indicator ? getComputedStyle(indicator.querySelector('svg') ?? indicator).color : null, titleColor: title ? getComputedStyle(title).color : null };
113
- }
114
-
115
- function measureMeter(el) {
116
- if ('grammarPart' in el.dataset) { return null; }
117
- const track = partIn(el, ['meter-track', 'track']) ?? el.querySelector('.meter__track');
118
- const band = el.parentElement;
119
- if (!track || !band) { return null; }
120
- const style = getComputedStyle(band);
121
- const trackRect = track.getBoundingClientRect();
122
- const segments = [...el.querySelectorAll('[data-grammar-part*="segment"]:not([data-grammar-part$="segments"]), .starci-core-meter-segment, [data-grammar-meter-segment]')]
123
- .map((part) => part.getBoundingClientRect()).map((rect) => ({ x: rect.left, width: rect.width }));
124
- const segmented = 'grammarMeterSegments' in el.dataset || 'segments' in el.dataset || segments.length > 1;
125
- return { desc: tagOf(el), segmented, track: { width: trackRect.width, height: trackRect.height },
126
- band: { width: band.clientWidth - Number.parseFloat(style.paddingLeft || '0') - Number.parseFloat(style.paddingRight || '0') }, segments };
127
- }
128
-
129
- function marked(el) {
130
- return 'component' in el.dataset || [...el.attributes].some((attr) => attr.name.startsWith('data-grammar-')) || [...el.classList].some((name) => name.startsWith('starci-core-'));
131
- }
132
-
133
- function paints(el) {
134
- const style = getComputedStyle(el);
135
- if (style.display === 'none' || style.visibility === 'hidden') { return false; }
136
- if (['img', 'svg', 'canvas', 'video', 'picture'].includes(el.tagName.toLowerCase())) { return true; }
137
- if ([...el.childNodes].some((node) => node.nodeType === 3 && node.textContent.trim())) { return true; }
138
- if (style.backgroundColor && !/^(?:transparent|rgba\(0, 0, 0, 0\))$/.test(style.backgroundColor)) { return true; }
139
- if (style.backgroundImage && style.backgroundImage !== 'none') { return true; }
140
- if (['Top', 'Right', 'Bottom', 'Left'].some((side) => Number.parseFloat(style[`border${side}Width`]) > 0 && style[`border${side}Style`] !== 'none' && !/rgba\(0, 0, 0, 0\)/.test(style[`border${side}Color`]))) { return true; }
141
- return Boolean(style.boxShadow && style.boxShadow !== 'none');
142
- }
143
-
144
- function measureOwnership(rootEl, layoutAttribute) {
145
- let ownership = null, dom = null;
146
- if (document.documentElement.dataset.drawHarness !== 'component' || !rootEl) { return { ownership, dom }; }
147
- const components = new Set();
148
- const unowned = [];
149
- let layoutElements = 0;
150
- for (const el of rootEl.querySelectorAll('*')) {
151
- if ('component' in el.dataset) { components.add(el.dataset.component); }
152
- layoutElements += Number(el.hasAttribute(layoutAttribute));
153
- if ((el.closest('svg') && el.tagName.toLowerCase() !== 'svg') || !paints(el)) { continue; }
154
- const owner = ownerOf(el, rootEl, layoutAttribute);
155
- if (owner !== 'grammar') { unowned.push(ownershipLabel(el, owner)); }
156
- }
157
- ownership = { components: [...components].sort((left, right) => { if (left < right) { return -1; } if (left > right) { return 1; } return 0; }), layoutElements, unownedCount: unowned.length, unowned: unowned.slice(0, 20) };
158
- const clone = document.documentElement.cloneNode(true);
159
- for (const el of clone.querySelectorAll('[data-component]')) {
160
- if (!('grammarComponent' in el.dataset)) { el.dataset.grammarComponent = el.dataset.component; }
161
- }
162
- for (const el of clone.querySelectorAll('script')) { el.remove(); }
163
- dom = `<!doctype html>
164
- ${clone.outerHTML}`;
165
- return { ownership, dom };
166
- }
167
-
168
- function ownerOf(el, rootEl, layoutAttribute) {
169
- for (let ancestor = el; ancestor && ancestor !== rootEl; ancestor = ancestor.parentElement) {
170
- if (marked(ancestor)) { return 'grammar'; }
171
- if (ancestor.hasAttribute(layoutAttribute)) { return 'layout'; }
172
- }
173
- return null;
174
- }
175
-
176
- function ownershipLabel(el, owner) {
177
- return el.tagName.toLowerCase() + (typeof el.className === 'string' && el.className ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.') : '') + (el.textContent.trim() ? ' "' + el.textContent.trim().slice(0, 40) + '"' : '') + ` (${owner ?? 'no owner'})`;
178
- }
179
-
180
- /** The expression that declares `functions` and returns what `entry` makes of `arg` (JSON data) in the page. */
181
- function pageScript(entry, functions, arg) {
182
- return `(() => {\n${functions.map(String).join('\n')}\nreturn ${entry.name}(${JSON.stringify(arg)});\n})()`;
183
- }
184
-
185
- /** What the page measures: `arg` is {generic, exemptSelector, layoutAttr}. */
186
- export function measureInPage(page, arg) {
187
- return page.evaluate(pageScript(measurePage, [measurePage, visible, measureFonts, measureOverflow, measureAccentExempt, measureArtwork, tagOf, partIn, measureAnatomy, measureAlert, measureMeter, marked, paints, measureOwnership, ownerOf, ownershipLabel], arg));
188
- }