diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46c6eacb..6ff183fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,63 @@ auto-generated per-PR notes; this file is the curated, human-readable history.
## [Unreleased]
### Added
+- **A reproducible UI-complexity measurement instrument** (#577) —
+ `build/ui-complexity-report.mjs` plus its pure, unit-tested half
+ (`build/ui-complexity-lib.mjs`) and the committed, auditable
+ `build/ui-complexity-manifest.json`. Dev tooling only: nothing from it enters
+ `dist/sql.html`. It exists because #577 (evaluate migrating the UI composition
+ layer to Preact) has to decide on evidence, and the two obvious instruments
+ both lie about this repository. Physical LOC does: the five shell-plumbing
+ modules total 1791 physical lines but **709 lines of code**, because each
+ invariant is documented next to the review bug that found it — so any arm
+ written at normal comment density "wins" while containing more code. Regex
+ counts of manual DOM mutation do too: run over raw source they match the prose
+ *inside* those comments, and three of the four families #577 names (`hidden`,
+ `dataset`, `replaceChildren`) go syntactically invisible under a vDOM, scoring
+ ~0 for a component arm by construction. So every count runs over
+ esbuild-transformed source — comments stripped, formatting normalized, by the
+ repo's own build tool — and the report emits its own metric tiering, so a
+ metric demoted to explanatory cannot be quietly promoted to a deciding one.
+ One canonical manifest is measured against every evaluation state via
+ `--manifest`, with an absent file reported rather than skipped: the two most
+ decision-relevant facts in the comparison are a file appearing and a file
+ disappearing, and per-state manifests would erase both. Tests include a
+ sabotage suite and a structural guard that the runner counts stripped code
+ rather than raw source, verified falsifiable. The S0 baseline and S1 control
+ measurements are pinned under `docs/design/577/`.
+
+- **The S2 Preact treatment arm, measured** (#577) — `docs/design/577/S2-EVIDENCE.md`
+ plus `docs/design/577/manifest-v2/`. The treatment lives on the unmerged
+ `eval-577/s2-treatment` branch (tag `577/treatment`): Preact owns shell
+ presentation and lifecycle, and `src/ui/app-shell.ts`, `ui/left-rail.ts` and
+ `ui/right-inspector.ts` are **deleted** — the esbuild per-input attribution in
+ `manifest-v2/s2/esbuild-inputs.json` is the proof they are absent from the
+ artifact rather than shipped beside the Preact one. Nothing from that branch is
+ merged; only this evidence is. All three states were re-measured in ONE run
+ from clean worktrees at their tagged SHAs over one 18-entry superset manifest,
+ because a treatment measured over a manifest omitting its own new files reports
+ a deletion with no matching cost. Result: **+330 code lines** and **+7.8 KB
+ gzip** against the S1 control for the same feature, with domain and island
+ lines byte-identical across all three states (the preservation constraint
+ holding). Repo-owned bytes in the artifact went **up** 3 536 B — deleting a
+ 905-line module did not shrink the shipped application. Exactly one copy of
+ `@preact/signals-core` is present, proven by per-package attribution rather
+ than assumed. The evidence document also records nine costs the control does
+ not pay (asynchronous DOM updates relative to state writes; a `flushSync`
+ escape hatch wherever a caller reveals a host then acts inside it; a defect
+ class no gate can see, where importing the signals core instead of the Preact
+ bindings leaves the shell rendering once and never repainting; and — the
+ sharpest — that derived state has no *edge*, so a `computed` can report a value
+ change but never that a gesture ended, which silently cost a pointer drag-fold
+ its focus rescue), seven genuine improvements, and the refutation of the
+ evaluation's own flagged weak premise that one owner per subtree deletes the
+ focus-rescue category. Two real regressions were caught by Chromium/WebKit e2e
+ with 6 995 unit tests green; two remaining e2e failures were verified to fail
+ identically on the frozen control and are scored against neither arm. The
+ recommendation itself is ADR-0004's, and is not made here: the parity suite,
+ the controlled-change experiment and the JSX sensitivity annex are still
+ outstanding.
+
- **The foldable left navigation is reachable in the UI** (#487, phase 3 of 4 —
the phase's own most complex wiring step). `app-shell.ts` composes phase 1's
pure mode/resize-session core and phase 2's section registry with a new
diff --git a/build/ui-complexity-lib.mjs b/build/ui-complexity-lib.mjs
new file mode 100644
index 00000000..c31e79c6
--- /dev/null
+++ b/build/ui-complexity-lib.mjs
@@ -0,0 +1,378 @@
+// UI-complexity report — pure half (issue #577).
+//
+// The measurement instrument the Preact evaluation (#577) decides on. Split
+// lib/runner exactly like size-report-lib.mjs / size-report.mjs so every
+// counting rule below is unit-testable without touching the filesystem or
+// esbuild.
+//
+// WHY THIS EXISTS, and why it is not a LOC counter:
+//
+// #577's decision principle asks whether a completed migration would REDUCE,
+// not merely reorganize, production UI code and lifecycle complexity. Two
+// naive instruments both lie about this repository:
+//
+// 1. Physical LOC. The five shell-plumbing modules total 1791 physical lines
+// but only ~709 lines of code — 989 are comment-only, because this repo
+// documents each invariant next to the review bug that found it. Any arm
+// written at normal comment density "wins" on physical LOC while
+// containing more code.
+// 2. Regex counts of manual DOM mutation (`.hidden =`, `.dataset.`,
+// `.replaceChildren(`). Three of those four families become
+// SYNTACTICALLY INVISIBLE under a vDOM whether or not the underlying
+// number of presentation decisions changed — a component arm scores ~0 by
+// construction. Worse, run over raw source they also match the prose IN
+// the comments, which is how a first pass at this measured 37
+// "mutation sites" in app-shell.ts.
+//
+// So: every count here runs over ESBUILD-TRANSFORMED source, never raw text.
+// esbuild is the repo's only build tool, it strips comments correctly where a
+// hand-rolled scanner does not (`//` inside a string, `\/\/` inside a regex
+// literal, a `*`-prefixed line inside a template literal, a trailing
+// `code; // note`), and it re-prints the code — which normalizes formatting, so
+// an arm cannot win by collapsing statements onto fewer lines. See
+// `tests/unit/ui-complexity-report.test.js` for the sabotage cases that hold
+// this claim.
+//
+// METRIC TIERING IS PART OF THE OUTPUT (`METRIC_TIERS` below). Two independent
+// plan reviews of #577 found that a report carrying ten unranked numbers lets
+// almost any outcome support either recommendation. The tier each metric sits
+// in is therefore emitted in the JSON, so a demoted metric cannot be quietly
+// promoted to a deciding one when the report is written.
+
+export const COMPLEXITY_SCHEMA_VERSION = 1;
+
+/**
+ * Which metrics may decide #577, and which may only explain it.
+ *
+ * `deciding` mirrors the four evidence classes in the approved plan. Three of
+ * them are NOT computable from source text and are therefore recorded here as
+ * declared-but-external: behavioural parity comes from the e2e suite, the
+ * artifact numbers come from `build/size-report.mjs`, and change amplification
+ * comes from the frozen controlled-change experiment. This instrument owns only
+ * `ownedProductionCode`.
+ *
+ * `explanatory` metrics are reported per file and per class but never voted
+ * with. Each carries the reason it was demoted, so the report cannot silently
+ * re-promote it:
+ * - `minifiedBytes` ignores cross-module tree shaking, shared helper
+ * generation, and complexity moved INTO a dependency;
+ * - `lcovBranches`/`lcovFunctions` fall when mechanisms move into Preact —
+ * that is externalized, not eliminated, complexity;
+ * - `lifecycleSites` is the vDOM-invisible family described above.
+ */
+export const METRIC_TIERS = {
+ deciding: {
+ ownedProductionCode: { owner: 'this instrument' },
+ parity: { owner: 'tests/e2e/shell-parity.spec.js' },
+ artifact: { owner: 'build/size-report.mjs' },
+ changeAmplification: { owner: 'frozen controlled-change experiment' },
+ cleanupObligations: { owner: 'leak invariant + manual obligation count' },
+ },
+ explanatory: {
+ minifiedBytes: { demotedBecause: 'ignores tree shaking, shared helpers, and complexity moved into a dependency' },
+ lcovBranches: { demotedBecause: 'falls when mechanisms move into Preact — externalized, not eliminated' },
+ lcovFunctions: { demotedBecause: 'falls when mechanisms move into Preact — externalized, not eliminated' },
+ lifecycleSites: { demotedBecause: 'hidden/dataset/replaceChildren go syntactically invisible under a vDOM' },
+ },
+};
+
+/** The three classes a manifest entry may declare. `domain` is preserved by
+ * both arms, `island` stays imperative under any render model (hard rule 5 —
+ * signals coordinate, they do not own mousemove), and only `plumbing` is
+ * genuinely in play. Mislabelling here is a wrong decision, not a wrong
+ * number, which is why the manifest is committed and the report names every
+ * file in it. */
+export const FILE_CLASSES = ['domain', 'plumbing', 'island'];
+
+/**
+ * Count the code-bearing lines of already-comment-stripped, re-printed source.
+ *
+ * Takes esbuild's `transform().code`, NOT raw source — the caller owns that
+ * step so this stays pure. Blank lines are excluded; a trailing newline does
+ * not count as a line.
+ */
+export function countNormalizedLines(transformedCode) {
+ const lines = String(transformedCode).split('\n');
+ let normalized = 0;
+ let blank = 0;
+ for (const line of lines) {
+ if (line.trim() === '') blank += 1;
+ else normalized += 1;
+ }
+ return { normalized, blank };
+}
+
+/**
+ * Occurrence counts (not line hits) of the lifecycle families #577's
+ * Measurements section names, over comment-stripped code.
+ *
+ * A first pass at this used `rg -c`, which counts matching LINES — so a line
+ * touching three of these families scored 1, and a comment discussing one
+ * scored 1 with no code at all. Both are fixed here: the caller passes stripped
+ * code, and every family is counted with a global regex.
+ */
+export function countLifecycleSites(strippedCode) {
+ const code = String(strippedCode);
+ const count = (re) => (code.match(re) || []).length;
+ return {
+ domMutation: count(/\.(?:hidden|textContent|value)\s*=|\.dataset\.[A-Za-z]/g)
+ + count(/\.(?:replaceChildren|append|setAttribute|removeAttribute)\(/g)
+ + count(/\.style\.[A-Za-z]+\s*=|\.classList\.[A-Za-z]+\(/g),
+ listener: count(/\.(?:add|remove)EventListener\(/g),
+ effect: count(/\b(?:effect|computed|untracked|batch)\(/g),
+ disposal: count(/\bdispose\w*\b/g),
+ focus: count(/\.focus\(\)/g),
+ };
+}
+
+/**
+ * Parse the `BRF`/`BRH`/`FNF`/`FNH` totals out of an lcov report, keyed by the
+ * `SF:` path exactly as lcov wrote it.
+ *
+ * Explanatory only (see `METRIC_TIERS`). Reported as "repo-owned executable
+ * test surface" rather than as a complexity verdict: a framework migration can
+ * cut these counts by moving mechanisms into Preact, which is a real reduction
+ * in code the project owns but not automatically a reduction in total
+ * lifecycle complexity.
+ */
+export function parseLcov(text) {
+ const byFile = {};
+ let current = null;
+ for (const rawLine of String(text).split('\n')) {
+ const line = rawLine.trim();
+ if (line.startsWith('SF:')) {
+ current = line.slice(3);
+ byFile[current] = { branchesFound: 0, branchesHit: 0, functionsFound: 0, functionsHit: 0 };
+ continue;
+ }
+ if (current === null) continue;
+ if (line.startsWith('BRF:')) byFile[current].branchesFound = Number(line.slice(4)) || 0;
+ else if (line.startsWith('BRH:')) byFile[current].branchesHit = Number(line.slice(4)) || 0;
+ else if (line.startsWith('FNF:')) byFile[current].functionsFound = Number(line.slice(4)) || 0;
+ else if (line.startsWith('FNH:')) byFile[current].functionsHit = Number(line.slice(4)) || 0;
+ else if (line === 'end_of_record') current = null;
+ }
+ return byFile;
+}
+
+/**
+ * Resolve a manifest path against lcov's own key set.
+ *
+ * lcov paths are absolute in this repo's coverage output while the manifest is
+ * repo-relative, so a plain lookup misses every file and would silently report
+ * zero branches everywhere — indistinguishable from "this file has no
+ * branches". Matching on a path suffix keeps that failure loud instead: an
+ * unmatched file is reported as `null`, never as 0.
+ */
+export function lookupLcov(lcovByFile, relPath) {
+ if (Object.prototype.hasOwnProperty.call(lcovByFile, relPath)) return lcovByFile[relPath];
+ const suffix = '/' + relPath;
+ for (const key of Object.keys(lcovByFile)) {
+ if (key.endsWith(suffix)) return lcovByFile[key];
+ }
+ return null;
+}
+
+/**
+ * Assemble the report from per-file measurements the runner has already taken.
+ *
+ * `entries` items: `{ path, class, sliceItem, normalizedLines,
+ * sourceBlankLines, physicalLines, minifiedBytes, lifecycle, lcov }`. Totals
+ * are grouped by
+ * class, because criterion 4 of #577 requires the report to distinguish
+ * product-specific domain logic from framework-like rendering plumbing — an
+ * undifferentiated grand total cannot do that.
+ */
+export function buildComplexityReport({ label, entries, environment = null }) {
+ const files = [...entries].sort((a, b) => a.path.localeCompare(b.path));
+ for (const entry of files) {
+ if (!FILE_CLASSES.includes(entry.class)) {
+ throw new Error(`ui-complexity: file "${entry.path}" has unknown class "${entry.class}" (expected one of ${FILE_CLASSES.join(', ')})`);
+ }
+ }
+ const emptyTotals = () => ({
+ files: 0,
+ // A manifest entry whose file does not exist in THIS state. Central to the
+ // method: all three evaluation states are measured against ONE canonical
+ // manifest, so the set is identical and the totals are comparable. S1 adds
+ // `right-inspector.ts` (absent in S0) and S2 DELETES `app-shell.ts` (absent
+ // there) — if each state used its own manifest, S1's cost and S2's deletion
+ // would both vanish from the comparison instead of showing up in it.
+ absentFiles: 0,
+ physicalLines: 0,
+ normalizedLines: 0,
+ nonCodeLines: 0,
+ minifiedBytes: 0,
+ lifecycle: { domMutation: 0, listener: 0, effect: 0, disposal: 0, focus: 0 },
+ lcovBranchesFound: 0,
+ lcovFunctionsFound: 0,
+ lcovUnmatchedFiles: 0,
+ });
+ const byClass = {};
+ const overall = emptyTotals();
+ for (const cls of FILE_CLASSES) byClass[cls] = emptyTotals();
+
+ for (const entry of files) {
+ for (const bucket of [byClass[entry.class], overall]) {
+ if (entry.absent) {
+ bucket.absentFiles += 1;
+ continue;
+ }
+ bucket.files += 1;
+ bucket.physicalLines += entry.physicalLines;
+ bucket.normalizedLines += entry.normalizedLines;
+ // NOT called "comment-only": esbuild RE-PRINTS the code, so its line
+ // count is not the source's code-line count and
+ // `physical - blank - normalized` is not a comment total. What it IS,
+ // exactly: source non-blank lines carrying no code after normalization —
+ // comments PLUS formatting-only lines (a lone `)`, a multi-line call
+ // esbuild re-joins). That is still the number a raw-LOC comparison would
+ // have wrongly credited as complexity, which is why it is reported; it is
+ // just not a comment count, and naming it one would be a measurement
+ // error in the metric this whole decision rests on.
+ bucket.nonCodeLines += entry.physicalLines - entry.sourceBlankLines - entry.normalizedLines;
+ bucket.minifiedBytes += entry.minifiedBytes;
+ for (const key of Object.keys(bucket.lifecycle)) bucket.lifecycle[key] += entry.lifecycle[key];
+ if (entry.lcov) {
+ bucket.lcovBranchesFound += entry.lcov.branchesFound;
+ bucket.lcovFunctionsFound += entry.lcov.functionsFound;
+ } else {
+ bucket.lcovUnmatchedFiles += 1;
+ }
+ }
+ }
+
+ return {
+ schemaVersion: COMPLEXITY_SCHEMA_VERSION,
+ label: label || null,
+ metricTiers: METRIC_TIERS,
+ environment,
+ // The deciding metric this instrument owns. Named explicitly so the report
+ // cannot substitute a different number for it.
+ ownedProductionCode: {
+ plumbingNormalizedLines: byClass.plumbing.normalizedLines,
+ islandNormalizedLines: byClass.island.normalizedLines,
+ domainNormalizedLines: byClass.domain.normalizedLines,
+ },
+ byClass,
+ overall,
+ files,
+ };
+}
+
+/** Absolute + percentage delta between two reports, for the same metric set.
+ * `null` percentage when the base is 0 — a 0 → n change has no meaningful
+ * percentage and printing `Infinity%` in a decision document is worse than
+ * printing nothing. */
+export function computeComplexityDelta(current, base) {
+ const abs = current - base;
+ return { current, base, abs, pct: base === 0 ? null : (abs / base) * 100 };
+}
+
+/** Diff two whole reports, per class and overall. */
+export function diffComplexityReports(current, base) {
+ const out = { byClass: {}, overall: {} };
+ const metrics = ['physicalLines', 'normalizedLines', 'nonCodeLines', 'minifiedBytes'];
+ for (const cls of FILE_CLASSES) {
+ out.byClass[cls] = {};
+ for (const metric of metrics) {
+ out.byClass[cls][metric] = computeComplexityDelta(current.byClass[cls][metric], base.byClass[cls][metric]);
+ }
+ }
+ for (const metric of metrics) {
+ out.overall[metric] = computeComplexityDelta(current.overall[metric], base.overall[metric]);
+ }
+ return out;
+}
+
+function formatPct(pct) {
+ if (pct === null) return 'n/a';
+ const sign = pct > 0 ? '+' : '';
+ return `${sign}${pct.toFixed(1)}%`;
+}
+
+function formatSigned(n) {
+ return (n > 0 ? '+' : '') + String(n);
+}
+
+/** Human-readable report. Deliberately leads with the tiering and with the
+ * physical-vs-normalized gap, because those two facts are what stop a reader
+ * from misreading the table underneath them. */
+export function renderComplexityMarkdown(report, deltas = null) {
+ const lines = [];
+ lines.push(`# UI complexity report${report.label ? ` — ${report.label}` : ''}`);
+ lines.push('');
+ lines.push(`Schema version ${report.schemaVersion}. Counts run over esbuild-transformed`);
+ lines.push('source (comments stripped, formatting normalized), never raw text.');
+ lines.push('');
+ lines.push('**Deciding metric owned here:** `ownedProductionCode` — normalized lines by class.');
+ lines.push('Every other number below is explanatory and must not be voted with:');
+ for (const [metric, meta] of Object.entries(report.metricTiers.explanatory)) {
+ lines.push(`- \`${metric}\` — ${meta.demotedBecause}`);
+ }
+ lines.push('');
+ lines.push('## Owned production code (deciding)');
+ lines.push('');
+ lines.push('| Class | Files | Normalized (code) | Physical | Non-code |');
+ lines.push('|---|---:|---:|---:|---:|');
+ for (const cls of FILE_CLASSES) {
+ const t = report.byClass[cls];
+ if (t.files === 0) continue;
+ lines.push(`| ${cls} | ${t.files} | **${t.normalizedLines}** | ${t.physicalLines} | ${t.nonCodeLines} |`);
+ }
+ const o = report.overall;
+ lines.push(`| **total** | **${o.files}** | **${o.normalizedLines}** | **${o.physicalLines}** | **${o.nonCodeLines}** |`);
+ lines.push('');
+ lines.push(`Physical LOC overstates code by **${o.nonCodeLines}** non-code lines`);
+ lines.push('(comments plus formatting-only lines). A raw-LOC comparison would have credited');
+ lines.push('those as complexity — which is the specific error this instrument exists to prevent.');
+ lines.push('');
+ lines.push('## Explanatory metrics');
+ lines.push('');
+ lines.push('| File | Class | Code | Minified B | DOM | Listeners | Effects | Disposal | focus() | lcov BRF/FNF |');
+ lines.push('|---|---|---:|---:|---:|---:|---:|---:|---:|---|');
+ for (const f of report.files) {
+ if (f.absent) {
+ // Explicitly present as a row rather than omitted: "this state does not
+ // have this file" is a measurement, and for S2 it is THE measurement.
+ lines.push(`| \`${f.path}\` | ${f.class} | _absent_ | — | — | — | — | — | — | — |`);
+ continue;
+ }
+ const lc = f.lcov ? `${f.lcov.branchesFound}/${f.lcov.functionsFound}` : '_unmatched_';
+ lines.push(`| \`${f.path}\` | ${f.class} | ${f.normalizedLines} | ${f.minifiedBytes} | ${f.lifecycle.domMutation} | ${f.lifecycle.listener} | ${f.lifecycle.effect} | ${f.lifecycle.disposal} | ${f.lifecycle.focus} | ${lc} |`);
+ }
+ if (report.overall.absentFiles > 0) {
+ lines.push('');
+ lines.push(`> ${report.overall.absentFiles} manifest file(s) absent in this state — measured against the one canonical`);
+ lines.push('> manifest so every state covers an identical file set (a deletion must show up, not vanish).');
+ }
+ if (o.lcovUnmatchedFiles > 0) {
+ lines.push('');
+ lines.push(`> ${o.lcovUnmatchedFiles} file(s) had no lcov record — reported as unmatched, never as zero branches.`);
+ }
+ if (deltas) {
+ lines.push('');
+ lines.push('## Delta vs base');
+ lines.push('');
+ lines.push('| Class | Normalized (code) | Δ | Minified B | Δ |');
+ lines.push('|---|---:|---:|---:|---:|');
+ for (const cls of FILE_CLASSES) {
+ const n = deltas.byClass[cls].normalizedLines;
+ const m = deltas.byClass[cls].minifiedBytes;
+ if (n.base === 0 && n.current === 0) continue;
+ lines.push(`| ${cls} | ${n.current} | ${formatSigned(n.abs)} (${formatPct(n.pct)}) | ${m.current} | ${formatSigned(m.abs)} (${formatPct(m.pct)}) |`);
+ }
+ const n = deltas.overall.normalizedLines;
+ lines.push(`| **total** | **${n.current}** | **${formatSigned(n.abs)} (${formatPct(n.pct)})** | ${deltas.overall.minifiedBytes.current} | ${formatSigned(deltas.overall.minifiedBytes.abs)} |`);
+ }
+ if (report.environment) {
+ lines.push('');
+ lines.push('## Environment');
+ lines.push('');
+ for (const [key, value] of Object.entries(report.environment)) {
+ lines.push(`- **${key}**: \`${value}\``);
+ }
+ }
+ lines.push('');
+ return lines.join('\n');
+}
diff --git a/build/ui-complexity-manifest.json b/build/ui-complexity-manifest.json
new file mode 100644
index 00000000..1d74e493
--- /dev/null
+++ b/build/ui-complexity-manifest.json
@@ -0,0 +1,92 @@
+{
+ "_comment": [
+ "The auditable file set for #577's UI-complexity measurement. Acceptance",
+ "criterion 4 requires the report to distinguish product-specific domain logic",
+ "from framework-like rendering plumbing, so THIS FILE is the artifact that",
+ "claim rests on — a wrong `class` here is a wrong decision, not a wrong",
+ "number. Every file the report names comes from here and nowhere else.",
+ "",
+ "Classes:",
+ " domain — preserved verbatim by both the vanilla control and the Preact",
+ " treatment. Any diff in these files during the evaluation is a",
+ " finding against #577's own 'preserve proven non-rendering",
+ " logic' constraint, not a licence.",
+ " plumbing — genuinely replaceable by a component model. This is the only",
+ " class the treatment may claim a reduction in.",
+ " island — stays imperative under ANY render model (CLAUDE.md hard rule 5:",
+ " signals coordinate state, they do not own every mousemove).",
+ " Counted so the report cannot present island code as a Preact",
+ " win or a Preact cost.",
+ "",
+ "The `sliceItem` field maps each file to the numbered item in #577's",
+ "'Evaluation scope' list it covers, so a reader can check the slice is",
+ "actually covered rather than taking the report's word for it.",
+ "",
+ "Classification rationale for the three non-obvious calls (a first pass at",
+ "this plan mislabelled all three, which would have overstated the",
+ "replaceable baseline by roughly 1000 physical lines):",
+ " - left-nav-separator.ts is an ISLAND, not plumbing: it is pointer/keyboard",
+ " mechanics plus resize-session bookkeeping. Only its thin ARIA/paint glue",
+ " is plumbing, and that is not separable at file granularity.",
+ " - nav-sections.ts and sidebar-upper.ts are DOMAIN shared verbatim: they are",
+ " NAV_SECTION_META plus persistent-host construction that BOTH arms need.",
+ " Only a ~10-line showSection is replaceable."
+ ],
+ "files": [
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching"
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation"
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations"
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher"
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics"
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free"
+ },
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam"
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host"
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes"
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)"
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector"
+ }
+ ]
+}
diff --git a/build/ui-complexity-report.mjs b/build/ui-complexity-report.mjs
new file mode 100644
index 00000000..b91dbb1a
--- /dev/null
+++ b/build/ui-complexity-report.mjs
@@ -0,0 +1,189 @@
+// UI-complexity report runner (issue #577).
+//
+// Measures the file set in `build/ui-complexity-manifest.json` and emits a
+// machine-readable JSON report plus a human-readable Markdown one, with
+// optional deltas against a prior report. The pure counting rules live in
+// `ui-complexity-lib.mjs` (unit-tested, including sabotage cases); this file
+// owns only filesystem access, the esbuild call, and environment capture.
+//
+// Usage: node build/ui-complexity-report.mjs [--out
] [--base ]
+// [--label ] [--manifest ]
+// --out output directory (default: complexity-report/)
+// --base a prior ui-complexity-report.json to diff against
+// --label a name for this measurement (e.g. baseline / control / treatment)
+// --manifest manifest to measure (default: build/ui-complexity-manifest.json)
+//
+// `--manifest` exists so ONE canonical manifest can be run against several
+// checked-out states. Every evaluation state must be measured over an IDENTICAL
+// file set or the totals are not comparable — and the two most decision-relevant
+// facts are precisely a file appearing (S1 adds the inspector) and a file
+// disappearing (S2 deletes the vanilla shell). A manifest entry whose file is
+// absent from the current checkout is reported as `absent`, never skipped, so a
+// deletion shows up in the comparison instead of vanishing from it.
+//
+// Reporting only — it reads source and never writes into src/.
+//
+// WHY esbuild AND NOT A REGEX: see ui-complexity-lib.mjs's header. Short
+// version: every count runs over comment-stripped, re-printed source, because
+// counting raw text both credits this repo's very high comment density as
+// complexity and matches the prose inside those comments.
+//
+// Environment capture is deliberately part of the report rather than a footnote:
+// #577's acceptance criterion 5 requires REPRODUCIBLE measurements, and a git
+// tag preserves source, not a build environment. The captured commit SHA is the
+// resolved one, never a tag name (tags can be moved).
+
+import { readFile, writeFile, mkdir } from 'node:fs/promises';
+import { execFileSync } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { dirname, resolve } from 'node:path';
+import { createHash } from 'node:crypto';
+import * as esbuild from 'esbuild';
+import {
+ buildComplexityReport,
+ countLifecycleSites,
+ countNormalizedLines,
+ diffComplexityReports,
+ lookupLcov,
+ parseLcov,
+ renderComplexityMarkdown,
+} from './ui-complexity-lib.mjs';
+
+const here = dirname(fileURLToPath(import.meta.url));
+const root = resolve(here, '..');
+
+function parseArgs(argv) {
+ const args = {
+ out: 'complexity-report', base: null, label: null,
+ manifest: 'build/ui-complexity-manifest.json',
+ };
+ for (let i = 0; i < argv.length; i += 1) {
+ if (argv[i] === '--out') args.out = argv[i + 1], i += 1;
+ else if (argv[i] === '--base') args.base = argv[i + 1], i += 1;
+ else if (argv[i] === '--label') args.label = argv[i + 1], i += 1;
+ else if (argv[i] === '--manifest') args.manifest = argv[i + 1], i += 1;
+ }
+ return args;
+}
+
+async function readJson(path) {
+ return JSON.parse(await readFile(path, 'utf8'));
+}
+
+/** Best-effort `git`/tool version capture. A missing value is recorded as
+ * 'unavailable' rather than omitted, so a report can never look like it
+ * captured an environment it did not. */
+function capture(command, args) {
+ try {
+ return execFileSync(command, args, { cwd: root, encoding: 'utf8' }).trim();
+ } catch {
+ return 'unavailable';
+ }
+}
+
+async function captureEnvironment() {
+ let lockHash = 'unavailable';
+ try {
+ const lock = await readFile(resolve(root, 'package-lock.json'));
+ lockHash = 'sha256:' + createHash('sha256').update(lock).digest('hex').slice(0, 16);
+ } catch { /* recorded as unavailable */ }
+ return {
+ // Resolved SHA, never a tag name — an annotated tag can be moved unless
+ // repository policy prevents it, so the SHA is what actually pins a state.
+ commit: capture('git', ['rev-parse', 'HEAD']),
+ commitDirty: capture('git', ['status', '--porcelain']) === '' ? 'clean' : 'DIRTY',
+ node: process.version,
+ esbuild: esbuild.version,
+ platform: `${process.platform}-${process.arch}`,
+ packageLock: lockHash,
+ };
+}
+
+async function measureFile(entry, lcovByFile) {
+ const abs = resolve(root, entry.path);
+ let source;
+ try {
+ source = await readFile(abs, 'utf8');
+ } catch {
+ // Absent in THIS state. Recorded, not skipped — see the `--manifest` note in
+ // the header: for the treatment state, the absence of the vanilla shell IS
+ // the headline measurement.
+ return { path: entry.path, class: entry.class, sliceItem: entry.sliceItem || null, absent: true };
+ }
+ const sourceLines = source.split('\n');
+ // A trailing newline yields a final empty element that is not a line.
+ const physicalLines = sourceLines.length > 0 && sourceLines[sourceLines.length - 1] === ''
+ ? sourceLines.length - 1
+ : sourceLines.length;
+ let sourceBlankLines = 0;
+ for (let i = 0; i < physicalLines; i += 1) {
+ if (sourceLines[i].trim() === '') sourceBlankLines += 1;
+ }
+
+ const loader = entry.path.endsWith('.ts') ? 'ts' : 'js';
+ const stripped = await esbuild.transform(source, { loader });
+ const minified = await esbuild.transform(source, { loader, minify: true });
+ const { normalized } = countNormalizedLines(stripped.code);
+
+ return {
+ path: entry.path,
+ class: entry.class,
+ sliceItem: entry.sliceItem || null,
+ physicalLines,
+ sourceBlankLines,
+ normalizedLines: normalized,
+ minifiedBytes: Buffer.byteLength(minified.code, 'utf8'),
+ // Counted over STRIPPED code, so a comment discussing `.hidden =` no longer
+ // inflates the number the way a raw-source regex did.
+ lifecycle: countLifecycleSites(stripped.code),
+ lcov: lookupLcov(lcovByFile, entry.path),
+ };
+}
+
+async function main() {
+ const args = parseArgs(process.argv.slice(2));
+ const manifest = await readJson(resolve(process.cwd(), args.manifest));
+
+ let lcovByFile = {};
+ try {
+ lcovByFile = parseLcov(await readFile(resolve(root, 'coverage/lcov.info'), 'utf8'));
+ } catch {
+ // No coverage run yet. Every file then reports lcov as unmatched, which the
+ // report prints as `_unmatched_` — never as zero branches, since "no data"
+ // and "no branches" are different claims.
+ }
+
+ const entries = [];
+ for (const entry of manifest.files) entries.push(await measureFile(entry, lcovByFile));
+
+ const report = buildComplexityReport({
+ label: args.label,
+ entries,
+ environment: await captureEnvironment(),
+ });
+
+ const base = args.base ? await readJson(resolve(process.cwd(), args.base)) : null;
+ const deltas = base ? diffComplexityReports(report, base) : null;
+
+ const outDir = resolve(process.cwd(), args.out);
+ await mkdir(outDir, { recursive: true });
+ await writeFile(resolve(outDir, 'ui-complexity-report.json'), JSON.stringify(report, null, 2) + '\n');
+ await writeFile(resolve(outDir, 'ui-complexity-report.md'), renderComplexityMarkdown(report, deltas));
+
+ const o = report.overall;
+ console.log(`ui-complexity report -> ${args.out}/`);
+ console.log(` ${o.files} files: ${o.normalizedLines} code lines (${o.physicalLines} physical, ${o.nonCodeLines} non-code)`);
+ console.log(` plumbing ${report.byClass.plumbing.normalizedLines} | domain ${report.byClass.domain.normalizedLines} | island ${report.byClass.island.normalizedLines}`);
+ if (o.absentFiles > 0) console.log(` ${o.absentFiles} manifest file(s) absent in this state`);
+ if (deltas) {
+ const n = deltas.overall.normalizedLines;
+ console.log(` Δ code lines vs base: ${n.abs > 0 ? '+' : ''}${n.abs}`);
+ } else {
+ console.log(' (no base report — deltas omitted)');
+ }
+ if (report.environment.commitDirty === 'DIRTY') {
+ console.log(' WARNING: working tree is dirty — this measurement is not reproducible from the recorded commit.');
+ }
+}
+
+await main();
diff --git a/docs/design/577/S2-EVIDENCE.md b/docs/design/577/S2-EVIDENCE.md
new file mode 100644
index 00000000..7a9582bd
--- /dev/null
+++ b/docs/design/577/S2-EVIDENCE.md
@@ -0,0 +1,287 @@
+# #577 state S2 — the Preact treatment arm, measured
+
+Evidence only. This document records what was built, what it cost, and what
+broke; it does **not** state the recommendation, which ADR-0004 owns once the
+parity suite and the controlled-change experiment exist.
+
+- **Arm:** `eval-577/s2-treatment`, tag `577/treatment`, commit `3f611b5e`.
+- **Control:** `eval-577/s1-control`, tag `577/control`, commit `7e7c8c4b`.
+- **Baseline:** `eval-577/s0-baseline`, tag `577/baseline`, commit `d3198478`.
+- Neither evaluation branch is merged. They are retained as reproducible
+ evidence under acceptance criterion 7 as amended 2026-07-30.
+
+## How to reproduce
+
+All three states were measured in one run, from **clean worktrees at their
+tagged SHAs**, over the **same 18-entry manifest**
+(`manifest-v2/canonical-manifest.json`). A dirty tree changes the artifact by
+exactly 6 bytes (`build/build.mjs`'s `-dirty` stamp), so a measurement from a
+working tree is not reproducible from the recorded commit.
+
+```sh
+git worktree add --detach 577/treatment
+ln -s /node_modules /node_modules
+cd
+node build/ui-complexity-report.mjs --manifest --out --label s2
+node build/size-report.mjs --out
+```
+
+The manifest is a **superset**: the seven `src/ui/shell/*` files report `absent`
+for S0 and S1, and `app-shell.ts` / `left-rail.ts` / `right-inspector.ts` report
+`absent` for S2. Measuring the treatment over a manifest that omitted its own
+new files would report a deletion with no matching cost, so the same file set is
+run against every state.
+
+The original 11-file reports in `s0/` and `s1/` are unchanged and still pinned;
+`manifest-v2/` supersedes them only for cross-state comparison.
+
+## Results
+
+Code lines are esbuild-normalized — comments stripped, formatting normalized —
+because this repo's comment density is institutional review history, not
+complexity, and counting raw text credits it as if it were.
+
+| | S0 baseline | S1 control | S2 treatment | S1 → S2 |
+|---|---:|---:|---:|---:|
+| code lines — plumbing | 599 | 742 | **1072** | **+330** |
+| code lines — domain | 341 | 341 | 341 | **0** |
+| code lines — island | 199 | 203 | 203 | **0** |
+| **code lines — total** | **1139** | **1286** | **1616** | **+330** |
+| artifact raw B | 2 126 521 | 2 130 619 | 2 149 976 | +19 357 |
+| artifact gzip B | 623 736 | 624 764 | 632 519 | **+7 755** |
+| artifact brotli B | 527 859 | 528 556 | 535 612 | +7 056 |
+| repo-owned bytes in bundle | — | 631 633 | 635 169 | **+3 536** |
+
+**Domain and island lines are identical across all three states.** That is the
+preservation constraint holding: the treatment changed no proven non-rendering
+logic, and it did not claim a reduction in code that stays imperative under any
+render model.
+
+### What the +7.8 KB gzip is made of
+
+Per-package attribution from the esbuild metafile:
+
+| package | S1 | S2 |
+|---|---:|---:|
+| `preact` | absent | 12 519 B |
+| `@preact/signals` | absent | 3 140 B |
+| `@preact/signals-core` | 4 713 B | 4 749 B |
+
+**Exactly one copy of `@preact/signals-core`** — the single-copy requirement is
+proven by attribution, not assumed. The Preact bindings re-export the same
+primitives over that one copy.
+
+The repo's **own** bytes in the artifact went **up** by 3 536 B. Deleting a
+905-line module did not shrink the shipped application.
+
+### The deletion is real
+
+`manifest-v2/s2/esbuild-inputs.json` is the per-input attribution for the S2
+artifact. It contains **no** `app-shell.ts`, `left-rail.ts` or
+`right-inspector.ts`, and all six `src/ui/shell/*` modules. The vanilla shell
+was deleted, not shipped alongside the Preact one.
+
+### A measurement correction worth recording
+
+S1's brotli figure here (528 556) differs from the 528 747 pinned earlier, while
+its raw and gzip figures match **exactly**. Identical raw and gzip prove the
+artifact bytes are identical; brotli output is not stable across Node versions.
+Re-measuring all three states in one run on one Node version is what makes the
+brotli column internally comparable — and is why the earlier figure should not
+be differenced against this one.
+
+## What the treatment cost
+
+Costs the control does not pay, each found while building:
+
+1. **DOM updates became asynchronous relative to state writes.** Eleven
+ assertions in `tests/unit/app.test.ts` needed an explicit flush. Not a test
+ artifact: any code that writes state and then reads the DOM is affected.
+2. **`showHost` needed a synchronous escape hatch.** Callers reveal a host and
+ then act inside it — `app.showQuerySurface()` immediately focuses the SQL
+ editor — and a deferred render left the editor unfocusable because its host
+ was still `hidden`. Fixed with a `flushSync` built on
+ `options.debounceRendering`. Every future boundary of that shape must
+ remember it.
+3. **A defect class with no gate coverage.** Importing `@preact/signals-core`
+ instead of `@preact/signals` leaves the reactivity bridge unwired: the shell
+ renders once and never repaints. `tsc`, `check-boundaries` and every
+ pure-signal test stayed green. Only rendering a real component caught it. The
+ imperative arm cannot have this failure mode — there is no bridge.
+4. **Derived state has no EDGE.** A `computed` reports that a *value* changed; it
+ cannot report that a *gesture ended*. `navMode` reaches its final value
+ mid-drag, while focus capture is correctly gated off — so the commit produced
+ no dependency change and a pointer drag-fold silently rescued no focus. The
+ commit had to be re-attached by hand. The control has no equivalent hazard:
+ its rescue lives in the paint function every trigger calls.
+5. **`drawer.ts`'s `attachDrawerResize` is not reusable** — it appends its handle
+ into the panel, which would put two owners on one subtree. The inspector
+ re-implements the drag. The control's reuse of that primitive was part of why
+ it stayed small.
+6. **Foreign DOM cannot carry a Preact prop.** A `.ri-tool-slot` wrapper plus a
+ CSS rule per preserved-but-hidden child, and the two vanilla-repainted
+ switcher rows keep their `hidden` writes as an effect.
+7. **Read placement became a performance-correctness rule.** Over 200 simulated
+ drag frames: the layout signal read by a leaf → root re-runs **1×**; the same
+ read moved to the root → root and rail re-run **201×**.
+8. **`widthRevision`.** `sidebarPx`/`leftNavDrawerPx` are plain fields, so a
+ width-only commit changes nothing a `computed` observes. Kept inside a
+ measured file rather than `state.ts` so the cost is counted.
+9. **Stale cross-references.** Ten-plus `domain` and `island` files still name
+ `app-shell.ts` in comments. They are deliberately not edited — a diff in a
+ domain file is a finding against the preservation constraint — so the arm
+ ships with dangling prose it is not allowed to fix.
+
+## What the treatment genuinely improved
+
+- `applyEffectiveLeftNavigationLayout` (~100 lines) and its four call sites are
+ gone; presentation is an expression of one `computed`.
+- The rail's four per-button `effect()`s and its whole `dispose()` are gone.
+- The inspector's `renderActive()` is gone — "exactly one tool exposed" is
+ structural.
+- The `isSessionActive()` guard every non-gesture trigger had to remember
+ becomes the shape of the data (`dragLayout` non-null).
+- The `untracked()` workaround (12 lines of comment in the control) is
+ unnecessary: a derivation with no side effects cannot widen its dependencies.
+- Focus **capture** collapses from four inline sites to one effect plus the
+ commit re-attachment.
+- One defensive guard becomes structurally **unreachable**: a focused drawer is
+ derived from a non-null section, so mode and section cannot disagree. The
+ control needed a real check because its mode lived in a DOM attribute.
+- The inspector's mobile projection is genuinely reactive. The control samples
+ `isMobile` once at mount and is therefore **stale** after a breakpoint
+ crossing — a latent control defect, left unfixed because the control is frozen,
+ and explicitly **not** counted as a treatment line saving.
+
+## The weak premise, tested
+
+> "One owner per subtree deletes the focus-rescue category."
+
+**Refuted** — but the first version of this document refuted it with an
+overbroad claim, corrected here after third-party review.
+
+*What was claimed:* "Preact offers no before-the-DOM-changes hook."
+*What is true:* Preact 10 has no such hook **for function components**, which is
+what this arm uses (and the modern idiom): `useLayoutEffect` cleanups run after
+the diff. But `preact/src/diff/index.js` calls `componentWillUpdate` (`:203`) and
+`getSnapshotBeforeUpdate` (`:250`) **before** `diffChildren` (`:259`) — gated on
+`isClassComponent`. A class-component arm could therefore read the DOM before it
+mutates. The frozen `577/treatment` branch carries the same overbroad phrasing in
+`focus-settlement.ts`'s header comment; it is left as-is because the tag pins
+measured evidence, and this document is the corrected record.
+
+**The premise is still refuted, and the correct reason is stronger.**
+`getSnapshotBeforeUpdate` *is* a capture hook — React's own canonical example for
+it is preserving scroll position across a mutation. Having it does not delete the
+capture/restore work; it relocates it into a lifecycle method and hands you the
+captured value back in `componentDidUpdate`. That is the same protocol
+`focus-settlement.ts` implements by hand, under a different name. What the
+component model changes is *where* this code lives, never *whether* it exists —
+and finding 4 above shows the relocation introduced a failure mode the control
+does not have.
+
+Adopting class components to reach that hook was not attempted, and would have
+carried its own cost: a second component paradigm inside an arm whose whole
+premise is that one render model replaces the imperative one.
+
+## Deviation from the plan
+
+The ship log's next-step wording said to delete "the geometry half of
+`left-nav-separator.ts`". **Not done, deliberately.** The canonical manifest
+classifies that file as an **island** because "only its thin ARIA/paint glue is
+plumbing, and that is not separable at file granularity", and hard rule 5 keeps
+high-frequency pointer surfaces imperative. Splitting it would have handed the
+treatment a line reduction it did not earn and put two writers on the same ARIA
+attributes. The island's 203 lines are identical in S1 and S2, which is the
+check that this deviation cost the treatment nothing it was owed.
+
+## Browser evidence
+
+Chromium and WebKit, full suite: **417 passed**. Two `tile-open-workbench`
+specs (`:327`, `:453`) fail on both engines, and fail **identically on
+`eval-577/s1-control` from a clean worktree** — so they do not differentiate the
+arms and are not scored against either.
+
+They are **not** pre-existing, and the first version of this document was wrong
+to imply it. `origin/main` passes all 21 specs in that file from a clean
+worktree. The failures are introduced by the **S1 control's own inspector**: it
+defaults to `inspectorPx` 420 (`state.ts`), so it occupies 420px as the last
+child of `.main-row`, narrowing the Dashboard grid enough to break two tile
+geometry assertions. S2 inherits the same pane and the same failures.
+
+That makes them a cost of the evaluation **slice**, not of either architecture —
+a mock pane added for the comparison perturbing unrelated layout assertions. It
+is worth recording for two reasons: the slice's own side effects are exactly the
+kind of thing that quietly contaminates a comparison, and #577's gate wording
+("passes every behavioural gate") is not cleanly met by *either* arm here.
+
+Checking this mattered twice over: an earlier S2 run failed a *different* subset
+of the same file, so it is partly order-dependent, and reading any single run as
+a verdict — without both the control and `main` as baselines — would have been
+wrong in both directions.
+
+Two genuine regressions were found by real-browser e2e **with 6 995 unit tests
+green**, and both are fixed:
+
+- a live width measurement silently became push-based, so a viewport-clamped
+ 420px preference rendered at its full 420 instead of 313;
+- an intent-gated focus restore did nothing, because a pointer drag drops the
+ focused descendant to `` on an intermediate `mousemove` frame, long
+ before any commit-time code runs.
+
+This is direct support for #577's insistence that the comparison be exercised in
+real browsers rather than in happy-dom alone.
+
+## Still outstanding before ADR-0004
+
+- the arm-agnostic parity suite (normalized subtree serialization, parametrized
+ over both states, pinned assertion count);
+- the controlled-change experiment;
+- the whole-slice JSX sensitivity annex;
+- **a re-verification path for the pinned numbers.** The evaluation branches are
+ pushed refs outside CI, so nothing re-runs the instrument against them. A
+ reviewer who wants to trust this table has to re-measure by hand (the
+ reproduction recipe above is exactly that). Either a scheduled check or an
+ explicit "these numbers are pinned, not gated" caveat belongs in ADR-0004.
+ Raised by third-party review.
+
+## Third-party review
+
+A ChatGPT pass over this package produced two substantive points. It did not
+finish generating within the time budget, so this is what it reached, each
+verified against the repo before being accepted:
+
+1. *A superset manifest is the right anti-omission device, but it does not by
+ itself prove every included adapter was necessary.* Fair, and **not closed by
+ this evidence**. File-by-file the seven `src/ui/shell/*` modules map to
+ distinct responsibilities with no duplication — but "counted fairly" is not
+ "no smaller design exists". The strongest candidate for reduction is
+ `shell-host.ts`, which carries 636 of the 1072 plumbing lines and deliberately
+ keeps nine bare `effect()` calls alongside the component tree; a
+ hooks-throughout arm would trade them for different code rather than less, but
+ that is an assertion this evaluation has not measured. The controlled-change
+ experiment is where it should be settled.
+2. *The lifecycle claim was overbroad.* Verified and corrected above.
+
+One further observation from verification rather than from ChatGPT: cost 3 (the
+unwired signals bridge) is the one cost that is arguably a **footgun rather than
+a property** — two packages with near-identical names, one of which silently does
+nothing. It is preventable with a lint rule, and a fair reading should weight it
+lower than the structural costs.
+
+## Reading against the precommitted rule
+
+The rule was fixed **before** any measurement and is reproduced verbatim in the
+ship log. It requires, for adoption, clear dominance on **both** (a) net
+repo-owned production shell code after counting adapters, and (b) lifecycle and
+focus ownership obligations *and* controlled change amplification — and states
+that **any** mixed result is a RETAIN recommendation.
+
+On (a) the treatment is **+330 code lines (+26% over the control)** and **+3 536
+repo-owned bytes** in the artifact. That is not a reduction, so (a) fails
+outright rather than mixing. (b) is not yet complete — the controlled-change
+experiment has not run — but it cannot rescue (a) under a rule that requires
+dominance on both.
+
+ADR-0004 records the recommendation. This document records only the numbers and
+what produced them.
diff --git a/docs/design/577/canonical-manifest.json b/docs/design/577/canonical-manifest.json
new file mode 100644
index 00000000..1d74e493
--- /dev/null
+++ b/docs/design/577/canonical-manifest.json
@@ -0,0 +1,92 @@
+{
+ "_comment": [
+ "The auditable file set for #577's UI-complexity measurement. Acceptance",
+ "criterion 4 requires the report to distinguish product-specific domain logic",
+ "from framework-like rendering plumbing, so THIS FILE is the artifact that",
+ "claim rests on — a wrong `class` here is a wrong decision, not a wrong",
+ "number. Every file the report names comes from here and nowhere else.",
+ "",
+ "Classes:",
+ " domain — preserved verbatim by both the vanilla control and the Preact",
+ " treatment. Any diff in these files during the evaluation is a",
+ " finding against #577's own 'preserve proven non-rendering",
+ " logic' constraint, not a licence.",
+ " plumbing — genuinely replaceable by a component model. This is the only",
+ " class the treatment may claim a reduction in.",
+ " island — stays imperative under ANY render model (CLAUDE.md hard rule 5:",
+ " signals coordinate state, they do not own every mousemove).",
+ " Counted so the report cannot present island code as a Preact",
+ " win or a Preact cost.",
+ "",
+ "The `sliceItem` field maps each file to the numbered item in #577's",
+ "'Evaluation scope' list it covers, so a reader can check the slice is",
+ "actually covered rather than taking the report's word for it.",
+ "",
+ "Classification rationale for the three non-obvious calls (a first pass at",
+ "this plan mislabelled all three, which would have overstated the",
+ "replaceable baseline by roughly 1000 physical lines):",
+ " - left-nav-separator.ts is an ISLAND, not plumbing: it is pointer/keyboard",
+ " mechanics plus resize-session bookkeeping. Only its thin ARIA/paint glue",
+ " is plumbing, and that is not separable at file granularity.",
+ " - nav-sections.ts and sidebar-upper.ts are DOMAIN shared verbatim: they are",
+ " NAV_SECTION_META plus persistent-host construction that BOTH arms need.",
+ " Only a ~10-line showSection is replaceable."
+ ],
+ "files": [
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching"
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation"
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations"
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher"
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics"
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free"
+ },
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam"
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host"
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes"
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)"
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector"
+ }
+ ]
+}
diff --git a/docs/design/577/manifest-v2/canonical-manifest.json b/docs/design/577/manifest-v2/canonical-manifest.json
new file mode 100644
index 00000000..5bda2270
--- /dev/null
+++ b/docs/design/577/manifest-v2/canonical-manifest.json
@@ -0,0 +1,140 @@
+{
+ "_comment": [
+ "The auditable file set for #577's UI-complexity measurement. Acceptance",
+ "criterion 4 requires the report to distinguish product-specific domain logic",
+ "from framework-like rendering plumbing, so THIS FILE is the artifact that",
+ "claim rests on — a wrong `class` here is a wrong decision, not a wrong",
+ "number. Every file the report names comes from here and nowhere else.",
+ "",
+ "Classes:",
+ " domain — preserved verbatim by both the vanilla control and the Preact",
+ " treatment. Any diff in these files during the evaluation is a",
+ " finding against #577's own 'preserve proven non-rendering",
+ " logic' constraint, not a licence.",
+ " plumbing — genuinely replaceable by a component model. This is the only",
+ " class the treatment may claim a reduction in.",
+ " island — stays imperative under ANY render model (CLAUDE.md hard rule 5:",
+ " signals coordinate state, they do not own every mousemove).",
+ " Counted so the report cannot present island code as a Preact",
+ " win or a Preact cost.",
+ "",
+ "The `sliceItem` field maps each file to the numbered item in #577's",
+ "'Evaluation scope' list it covers, so a reader can check the slice is",
+ "actually covered rather than taking the report's word for it.",
+ "",
+ "Classification rationale for the three non-obvious calls (a first pass at",
+ "this plan mislabelled all three, which would have overstated the",
+ "replaceable baseline by roughly 1000 physical lines):",
+ " - left-nav-separator.ts is an ISLAND, not plumbing: it is pointer/keyboard",
+ " mechanics plus resize-session bookkeeping. Only its thin ARIA/paint glue",
+ " is plumbing, and that is not separable at file granularity.",
+ " - nav-sections.ts and sidebar-upper.ts are DOMAIN shared verbatim: they are",
+ " NAV_SECTION_META plus persistent-host construction that BOTH arms need.",
+ " Only a ~10-line showSection is replaceable.",
+ "",
+ "#577 state S2 extends this manifest with the seven src/ui/shell/* files the",
+ "Preact treatment adds. The manifest is a SUPERSET measured identically against",
+ "every state: the new entries report `absent` for S0 and S1, and app-shell.ts /",
+ "left-rail.ts / right-inspector.ts report `absent` for S2. That is the only way",
+ "the three states share one file set — a treatment measured over a manifest that",
+ "omitted its own new files would be reporting a deletion with no matching cost.",
+ "S0 and S1 were RE-MEASURED over this extended manifest from their own tags; the",
+ "original 11-file reports remain pinned beside them unchanged.",
+ "",
+ "shell-context.types.ts is type-only: esbuild erases it entirely, so it measures",
+ "0 code lines. It is listed anyway rather than quietly excluded — a reader can",
+ "see it costs nothing, which is a claim, not an omission."
+ ],
+ "files": [
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching"
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation"
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations"
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher"
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics"
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free"
+ },
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam"
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host"
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes"
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)"
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector"
+ },
+ {
+ "path": "src/ui/shell/shell-host.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — the composition root: shell geometry below the header, desktop/mobile projection, Query/Dashboard host switching (replaces app-shell.ts)"
+ },
+ {
+ "path": "src/ui/shell/shell-view.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 2, 5, 6 — the rendered application frame, the left navigation in wide/rail/drawer presentations, and the mobile bottom nav (replaces app-shell.ts markup + left-rail.ts)"
+ },
+ {
+ "path": "src/ui/shell/shell-layout.ts",
+ "class": "plumbing",
+ "sliceItem": "2, 3 — the left-navigation layout as derived state (replaces applyEffectiveLeftNavigationLayout)"
+ },
+ {
+ "path": "src/ui/shell/focus-settlement.ts",
+ "class": "plumbing",
+ "sliceItem": "3 — capture/settle across a structural transition where the focused source disappears"
+ },
+ {
+ "path": "src/ui/shell/right-inspector-view.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host (replaces right-inspector.ts)"
+ },
+ {
+ "path": "src/ui/shell/adopt.ts",
+ "class": "plumbing",
+ "sliceItem": "7 — the Preact/foreign-DOM boundary every imperative integration point crosses"
+ },
+ {
+ "path": "src/ui/shell/shell-context.types.ts",
+ "class": "plumbing",
+ "sliceItem": "1 — type-only seam contract for the view; erased by the build"
+ }
+ ]
+}
diff --git a/docs/design/577/manifest-v2/s0/bundle-size-report.json b/docs/design/577/manifest-v2/s0/bundle-size-report.json
new file mode 100644
index 00000000..6e352232
--- /dev/null
+++ b/docs/design/577/manifest-v2/s0/bundle-size-report.json
@@ -0,0 +1,386 @@
+{
+ "schemaVersion": 1,
+ "artifact": {
+ "raw": 2126521,
+ "gzip": 623736,
+ "brotli": 527859
+ },
+ "js": {
+ "raw": 1868626,
+ "gzip": 508303,
+ "brotli": 419075
+ },
+ "css": {
+ "raw": 237839,
+ "gzip": 109628,
+ "brotli": 105834
+ },
+ "totalOutputBytes": 1868430,
+ "entryPoints": [
+ {
+ "file": "main.js",
+ "entryPoint": "src/main.ts",
+ "bytes": 1868623
+ }
+ ],
+ "ownership": {
+ "project": {
+ "bytes": 629424,
+ "pct": 33.687320370578504
+ },
+ "generated": {
+ "bytes": 457367,
+ "pct": 24.47867996125089
+ },
+ "external": {
+ "bytes": 781639,
+ "pct": 41.8339996681706
+ },
+ "other": {
+ "bytes": 0,
+ "pct": 0
+ }
+ },
+ "packages": [
+ {
+ "name": "chart.js",
+ "bytes": 197174,
+ "pct": 10.55292411275777
+ },
+ {
+ "name": "@codemirror/view",
+ "bytes": 186268,
+ "pct": 9.969225499483523
+ },
+ {
+ "name": "date-fns",
+ "bytes": 47998,
+ "pct": 2.5688947405040596
+ },
+ {
+ "name": "@codemirror/state",
+ "bytes": 46747,
+ "pct": 2.5019401315542993
+ },
+ {
+ "name": "marked",
+ "bytes": 41843,
+ "pct": 2.2394737828016034
+ },
+ {
+ "name": "@dagrejs/dagre",
+ "bytes": 40422,
+ "pct": 2.163420625873059
+ },
+ {
+ "name": "@codemirror/lang-sql",
+ "bytes": 32138,
+ "pct": 1.720053734953945
+ },
+ {
+ "name": "@codemirror/autocomplete",
+ "bytes": 31522,
+ "pct": 1.6870848787484678
+ },
+ {
+ "name": "@lezer/lr",
+ "bytes": 26552,
+ "pct": 1.421086152545185
+ },
+ {
+ "name": "@codemirror/language",
+ "bytes": 24486,
+ "pct": 1.3105120341677237
+ },
+ {
+ "name": "@codemirror/commands",
+ "bytes": 23371,
+ "pct": 1.250836263600991
+ },
+ {
+ "name": "@lezer/common",
+ "bytes": 20388,
+ "pct": 1.091183507008558
+ },
+ {
+ "name": "@codemirror/search",
+ "bytes": 18472,
+ "pct": 0.9886375192006123
+ },
+ {
+ "name": "@lezer/xml",
+ "bytes": 8569,
+ "pct": 0.45862033900119353
+ },
+ {
+ "name": "@kurkle/color",
+ "bytes": 7597,
+ "pct": 0.40659805291073253
+ },
+ {
+ "name": "@lezer/highlight",
+ "bytes": 7178,
+ "pct": 0.38417280818655236
+ },
+ {
+ "name": "@codemirror/lang-xml",
+ "bytes": 5975,
+ "pct": 0.31978720101903735
+ },
+ {
+ "name": "@preact/signals-core",
+ "bytes": 4713,
+ "pct": 0.25224386249417957
+ },
+ {
+ "name": "@marijn/find-cluster-break",
+ "bytes": 2332,
+ "pct": 0.1248106699207356
+ },
+ {
+ "name": "style-mod",
+ "bytes": 2202,
+ "pct": 0.11785295676048875
+ },
+ {
+ "name": "chartjs-adapter-date-fns",
+ "bytes": 1688,
+ "pct": 0.09034322934228202
+ },
+ {
+ "name": "@lezer/json",
+ "bytes": 1599,
+ "pct": 0.08557987187103612
+ },
+ {
+ "name": "w3c-keyname",
+ "bytes": 1520,
+ "pct": 0.08135172310442457
+ },
+ {
+ "name": "crelt",
+ "bytes": 611,
+ "pct": 0.03270125185316014
+ },
+ {
+ "name": "@codemirror/lang-json",
+ "bytes": 274,
+ "pct": 0.014664718506981797
+ }
+ ],
+ "topModules": [
+ {
+ "path": "src/generated/json-schema-validators.js",
+ "bytes": 214595,
+ "pct": 11.485311197101309,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "src/generated/example-dashboards.ts",
+ "bytes": 187686,
+ "pct": 10.045118093800678,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/view/dist/index.js",
+ "bytes": 186268,
+ "pct": 9.969225499483523,
+ "owner": "external",
+ "group": "@codemirror/view"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chart.js",
+ "bytes": 165504,
+ "pct": 8.857918145180713,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "src/generated/json-schemas.js",
+ "bytes": 55086,
+ "pct": 2.9482506703489024,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/state/dist/index.js",
+ "bytes": 46747,
+ "pct": 2.5019401315542993,
+ "owner": "external",
+ "group": "@codemirror/state"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/marked/lib/marked.esm.js",
+ "bytes": 41843,
+ "pct": 2.2394737828016034,
+ "owner": "external",
+ "group": "marked"
+ },
+ {
+ "path": "src/ui/app.ts",
+ "bytes": 41314,
+ "pct": 2.211161242326445,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@dagrejs/dagre/dist/dagre.esm.js",
+ "bytes": 40422,
+ "pct": 2.163420625873059,
+ "owner": "external",
+ "group": "@dagrejs/dagre"
+ },
+ {
+ "path": "src/ui/dashboard.ts",
+ "bytes": 35268,
+ "pct": 1.8875740595045039,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-sql/dist/index.js",
+ "bytes": 32138,
+ "pct": 1.720053734953945,
+ "owner": "external",
+ "group": "@codemirror/lang-sql"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chunks/helpers.dataset.js",
+ "bytes": 31641,
+ "pct": 1.693453862333617,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/autocomplete/dist/index.js",
+ "bytes": 31522,
+ "pct": 1.6870848787484678,
+ "owner": "external",
+ "group": "@codemirror/autocomplete"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/lr/dist/index.js",
+ "bytes": 26552,
+ "pct": 1.421086152545185,
+ "owner": "external",
+ "group": "@lezer/lr"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/language/dist/index.js",
+ "bytes": 24486,
+ "pct": 1.3105120341677237,
+ "owner": "external",
+ "group": "@codemirror/language"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/commands/dist/index.js",
+ "bytes": 23371,
+ "pct": 1.250836263600991,
+ "owner": "external",
+ "group": "@codemirror/commands"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/common/dist/index.js",
+ "bytes": 20388,
+ "pct": 1.091183507008558,
+ "owner": "external",
+ "group": "@lezer/common"
+ },
+ {
+ "path": "src/ui/results.ts",
+ "bytes": 18828,
+ "pct": 1.0076909490855959,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/search/dist/index.js",
+ "bytes": 18472,
+ "pct": 0.9886375192006123,
+ "owner": "external",
+ "group": "@codemirror/search"
+ },
+ {
+ "path": "src/dashboard/application/dashboard-viewer-session.ts",
+ "bytes": 15288,
+ "pct": 0.8182270676450282,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/dashboard-tree.ts",
+ "bytes": 13899,
+ "pct": 0.7438865785713139,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/file-menu.ts",
+ "bytes": 12919,
+ "pct": 0.6914361255171454,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/core/chart-data.ts",
+ "bytes": 12028,
+ "pct": 0.6437490299342229,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/explain-graph.ts",
+ "bytes": 11980,
+ "pct": 0.6411800281519778,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/state.ts",
+ "bytes": 10933,
+ "pct": 0.585143676776759,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/shortcuts.ts",
+ "bytes": 10307,
+ "pct": 0.5516396118666475,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/net/ch-client.ts",
+ "bytes": 9860,
+ "pct": 0.5277157827694909,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/dashboard/model/workspace-semantics.ts",
+ "bytes": 8712,
+ "pct": 0.46627382347746504,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/xml/dist/index.js",
+ "bytes": 8569,
+ "pct": 0.45862033900119353,
+ "owner": "external",
+ "group": "@lezer/xml"
+ },
+ {
+ "path": "src/core/spec-schema.ts",
+ "bytes": 8272,
+ "pct": 0.44272464047355264,
+ "owner": "project",
+ "group": "src"
+ }
+ ],
+ "notes": [
+ "Percentages are of raw contributed output bytes (metafile bytesInOutput); gzip/Brotli are measured per whole artifact only — compression is not additive across modules."
+ ]
+}
\ No newline at end of file
diff --git a/docs/design/577/manifest-v2/s0/ui-complexity-report.json b/docs/design/577/manifest-v2/s0/ui-complexity-report.json
new file mode 100644
index 00000000..474511ce
--- /dev/null
+++ b/docs/design/577/manifest-v2/s0/ui-complexity-report.json
@@ -0,0 +1,344 @@
+{
+ "schemaVersion": 1,
+ "label": "s0",
+ "metricTiers": {
+ "deciding": {
+ "ownedProductionCode": {
+ "owner": "this instrument"
+ },
+ "parity": {
+ "owner": "tests/e2e/shell-parity.spec.js"
+ },
+ "artifact": {
+ "owner": "build/size-report.mjs"
+ },
+ "changeAmplification": {
+ "owner": "frozen controlled-change experiment"
+ },
+ "cleanupObligations": {
+ "owner": "leak invariant + manual obligation count"
+ }
+ },
+ "explanatory": {
+ "minifiedBytes": {
+ "demotedBecause": "ignores tree shaking, shared helpers, and complexity moved into a dependency"
+ },
+ "lcovBranches": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lcovFunctions": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lifecycleSites": {
+ "demotedBecause": "hidden/dataset/replaceChildren go syntactically invisible under a vDOM"
+ }
+ }
+ },
+ "environment": {
+ "commit": "d319847875b3b3eeff2c4ca4c43fd7dda12f8b40",
+ "commitDirty": "clean",
+ "node": "v25.9.0",
+ "esbuild": "0.28.1",
+ "platform": "darwin-arm64",
+ "packageLock": "sha256:9042ec0421903e38"
+ },
+ "ownedProductionCode": {
+ "plumbingNormalizedLines": 599,
+ "islandNormalizedLines": 199,
+ "domainNormalizedLines": 341
+ },
+ "byClass": {
+ "domain": {
+ "files": 4,
+ "absentFiles": 0,
+ "physicalLines": 1367,
+ "normalizedLines": 341,
+ "nonCodeLines": 953,
+ "minifiedBytes": 8984,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 4
+ },
+ "plumbing": {
+ "files": 4,
+ "absentFiles": 8,
+ "physicalLines": 1470,
+ "normalizedLines": 599,
+ "nonCodeLines": 817,
+ "minifiedBytes": 15143,
+ "lifecycle": {
+ "domMutation": 60,
+ "listener": 7,
+ "effect": 17,
+ "disposal": 34,
+ "focus": 7
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 4
+ },
+ "island": {
+ "files": 2,
+ "absentFiles": 0,
+ "physicalLines": 590,
+ "normalizedLines": 199,
+ "nonCodeLines": 350,
+ "minifiedBytes": 4156,
+ "lifecycle": {
+ "domMutation": 14,
+ "listener": 18,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 2
+ }
+ },
+ "overall": {
+ "files": 10,
+ "absentFiles": 8,
+ "physicalLines": 3427,
+ "normalizedLines": 1139,
+ "nonCodeLines": 2120,
+ "minifiedBytes": 28283,
+ "lifecycle": {
+ "domMutation": 84,
+ "listener": 25,
+ "effect": 21,
+ "disposal": 38,
+ "focus": 7
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 10
+ },
+ "files": [
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam",
+ "physicalLines": 175,
+ "sourceBlankLines": 8,
+ "normalizedLines": 48,
+ "minifiedBytes": 951,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free",
+ "physicalLines": 853,
+ "sourceBlankLines": 39,
+ "normalizedLines": 150,
+ "minifiedBytes": 4836,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching",
+ "physicalLines": 880,
+ "sourceBlankLines": 30,
+ "normalizedLines": 328,
+ "minifiedBytes": 7603,
+ "lifecycle": {
+ "domMutation": 39,
+ "listener": 3,
+ "effect": 14,
+ "disposal": 22,
+ "focus": 4
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes",
+ "physicalLines": 144,
+ "sourceBlankLines": 7,
+ "normalizedLines": 56,
+ "minifiedBytes": 993,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics",
+ "physicalLines": 443,
+ "sourceBlankLines": 29,
+ "normalizedLines": 157,
+ "minifiedBytes": 3170,
+ "lifecycle": {
+ "domMutation": 12,
+ "listener": 14,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation",
+ "physicalLines": 129,
+ "sourceBlankLines": 8,
+ "normalizedLines": 51,
+ "minifiedBytes": 775,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 1,
+ "disposal": 6,
+ "focus": 3
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations",
+ "physicalLines": 202,
+ "sourceBlankLines": 13,
+ "normalizedLines": 69,
+ "minifiedBytes": 1464,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/adopt.ts",
+ "class": "plumbing",
+ "sliceItem": "7 — the Preact/foreign-DOM boundary every imperative integration point crosses",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/focus-settlement.ts",
+ "class": "plumbing",
+ "sliceItem": "3 — capture/settle across a structural transition where the focused source disappears",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/right-inspector-view.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host (replaces right-inspector.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-context.types.ts",
+ "class": "plumbing",
+ "sliceItem": "1 — type-only seam contract for the view; erased by the build",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-host.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — the composition root: shell geometry below the header, desktop/mobile projection, Query/Dashboard host switching (replaces app-shell.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-layout.ts",
+ "class": "plumbing",
+ "sliceItem": "2, 3 — the left-navigation layout as derived state (replaces applyEffectiveLeftNavigationLayout)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-view.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 2, 5, 6 — the rendered application frame, the left navigation in wide/rail/drawer presentations, and the mobile bottom nav (replaces app-shell.ts markup + left-rail.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher",
+ "physicalLines": 137,
+ "sourceBlankLines": 13,
+ "normalizedLines": 74,
+ "minifiedBytes": 1733,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector",
+ "physicalLines": 147,
+ "sourceBlankLines": 12,
+ "normalizedLines": 42,
+ "minifiedBytes": 986,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 4,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host",
+ "physicalLines": 317,
+ "sourceBlankLines": 9,
+ "normalizedLines": 164,
+ "minifiedBytes": 5772,
+ "lifecycle": {
+ "domMutation": 17,
+ "listener": 4,
+ "effect": 2,
+ "disposal": 6,
+ "focus": 0
+ },
+ "lcov": null
+ }
+ ]
+}
diff --git a/docs/design/577/manifest-v2/s0/ui-complexity-report.md b/docs/design/577/manifest-v2/s0/ui-complexity-report.md
new file mode 100644
index 00000000..056aa73a
--- /dev/null
+++ b/docs/design/577/manifest-v2/s0/ui-complexity-report.md
@@ -0,0 +1,61 @@
+# UI complexity report — s0
+
+Schema version 1. Counts run over esbuild-transformed
+source (comments stripped, formatting normalized), never raw text.
+
+**Deciding metric owned here:** `ownedProductionCode` — normalized lines by class.
+Every other number below is explanatory and must not be voted with:
+- `minifiedBytes` — ignores tree shaking, shared helpers, and complexity moved into a dependency
+- `lcovBranches` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lcovFunctions` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lifecycleSites` — hidden/dataset/replaceChildren go syntactically invisible under a vDOM
+
+## Owned production code (deciding)
+
+| Class | Files | Normalized (code) | Physical | Non-code |
+|---|---:|---:|---:|---:|
+| domain | 4 | **341** | 1367 | 953 |
+| plumbing | 4 | **599** | 1470 | 817 |
+| island | 2 | **199** | 590 | 350 |
+| **total** | **10** | **1139** | **3427** | **2120** |
+
+Physical LOC overstates code by **2120** non-code lines
+(comments plus formatting-only lines). A raw-LOC comparison would have credited
+those as complexity — which is the specific error this instrument exists to prevent.
+
+## Explanatory metrics
+
+| File | Class | Code | Minified B | DOM | Listeners | Effects | Disposal | focus() | lcov BRF/FNF |
+|---|---|---:|---:|---:|---:|---:|---:|---:|---|
+| `src/application/left-nav.ts` | domain | 48 | 951 | 4 | 0 | 2 | 0 | 0 | _unmatched_ |
+| `src/core/left-nav-layout.ts` | domain | 150 | 4836 | 0 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/app-shell.ts` | plumbing | 328 | 7603 | 39 | 3 | 14 | 22 | 4 | _unmatched_ |
+| `src/ui/drawer.ts` | plumbing | 56 | 993 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/left-nav-separator.ts` | island | 157 | 3170 | 12 | 14 | 2 | 4 | 0 | _unmatched_ |
+| `src/ui/left-rail.ts` | plumbing | 51 | 775 | 2 | 0 | 1 | 6 | 3 | _unmatched_ |
+| `src/ui/nav-sections.ts` | domain | 69 | 1464 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/right-inspector.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/adopt.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/focus-settlement.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/right-inspector-view.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-context.types.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-host.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-layout.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-view.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/sidebar-upper.ts` | domain | 74 | 1733 | 4 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/splitters.ts` | island | 42 | 986 | 2 | 4 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/workbench/workbench-shell.ts` | plumbing | 164 | 5772 | 17 | 4 | 2 | 6 | 0 | _unmatched_ |
+
+> 8 manifest file(s) absent in this state — measured against the one canonical
+> manifest so every state covers an identical file set (a deletion must show up, not vanish).
+
+> 10 file(s) had no lcov record — reported as unmatched, never as zero branches.
+
+## Environment
+
+- **commit**: `d319847875b3b3eeff2c4ca4c43fd7dda12f8b40`
+- **commitDirty**: `clean`
+- **node**: `v25.9.0`
+- **esbuild**: `0.28.1`
+- **platform**: `darwin-arm64`
+- **packageLock**: `sha256:9042ec0421903e38`
diff --git a/docs/design/577/manifest-v2/s1/bundle-size-report.json b/docs/design/577/manifest-v2/s1/bundle-size-report.json
new file mode 100644
index 00000000..03824d72
--- /dev/null
+++ b/docs/design/577/manifest-v2/s1/bundle-size-report.json
@@ -0,0 +1,386 @@
+{
+ "schemaVersion": 1,
+ "artifact": {
+ "raw": 2130619,
+ "gzip": 624764,
+ "brotli": 528556
+ },
+ "js": {
+ "raw": 1870835,
+ "gzip": 509125,
+ "brotli": 420019
+ },
+ "css": {
+ "raw": 239728,
+ "gzip": 109836,
+ "brotli": 105960
+ },
+ "totalOutputBytes": 1870639,
+ "entryPoints": [
+ {
+ "file": "main.js",
+ "entryPoint": "src/main.ts",
+ "bytes": 1870832
+ }
+ ],
+ "ownership": {
+ "project": {
+ "bytes": 631633,
+ "pct": 33.7656276812362
+ },
+ "generated": {
+ "bytes": 457367,
+ "pct": 24.449773580044038
+ },
+ "external": {
+ "bytes": 781639,
+ "pct": 41.784598738719765
+ },
+ "other": {
+ "bytes": 0,
+ "pct": 0
+ }
+ },
+ "packages": [
+ {
+ "name": "chart.js",
+ "bytes": 197174,
+ "pct": 10.540462376760026
+ },
+ {
+ "name": "@codemirror/view",
+ "bytes": 186268,
+ "pct": 9.957453041447334
+ },
+ {
+ "name": "date-fns",
+ "bytes": 47998,
+ "pct": 2.5658611843332677
+ },
+ {
+ "name": "@codemirror/state",
+ "bytes": 46747,
+ "pct": 2.498985640735599
+ },
+ {
+ "name": "marked",
+ "bytes": 41843,
+ "pct": 2.236829233219237
+ },
+ {
+ "name": "@dagrejs/dagre",
+ "bytes": 40422,
+ "pct": 2.160865885935234
+ },
+ {
+ "name": "@codemirror/lang-sql",
+ "bytes": 32138,
+ "pct": 1.718022558067056
+ },
+ {
+ "name": "@codemirror/autocomplete",
+ "bytes": 31522,
+ "pct": 1.6850926341212817
+ },
+ {
+ "name": "@lezer/lr",
+ "bytes": 26552,
+ "pct": 1.419408020467872
+ },
+ {
+ "name": "@codemirror/language",
+ "bytes": 24486,
+ "pct": 1.3089644768445434
+ },
+ {
+ "name": "@codemirror/commands",
+ "bytes": 23371,
+ "pct": 1.2493591761959415
+ },
+ {
+ "name": "@lezer/common",
+ "bytes": 20388,
+ "pct": 1.089894950335153
+ },
+ {
+ "name": "@codemirror/search",
+ "bytes": 18472,
+ "pct": 0.9874700570232953
+ },
+ {
+ "name": "@lezer/xml",
+ "bytes": 8569,
+ "pct": 0.4580787634599728
+ },
+ {
+ "name": "@kurkle/color",
+ "bytes": 7597,
+ "pct": 0.40611790944164
+ },
+ {
+ "name": "@lezer/highlight",
+ "bytes": 7178,
+ "pct": 0.38371914623826403
+ },
+ {
+ "name": "@codemirror/lang-xml",
+ "bytes": 5975,
+ "pct": 0.3194095707402657
+ },
+ {
+ "name": "@preact/signals-core",
+ "bytes": 4713,
+ "pct": 0.25194599278642216
+ },
+ {
+ "name": "@marijn/find-cluster-break",
+ "bytes": 2332,
+ "pct": 0.12466328350900416
+ },
+ {
+ "name": "style-mod",
+ "bytes": 2202,
+ "pct": 0.11771378657239584
+ },
+ {
+ "name": "chartjs-adapter-date-fns",
+ "bytes": 1688,
+ "pct": 0.09023654483842151
+ },
+ {
+ "name": "@lezer/json",
+ "bytes": 1599,
+ "pct": 0.085478812320282
+ },
+ {
+ "name": "w3c-keyname",
+ "bytes": 1520,
+ "pct": 0.08125565648957389
+ },
+ {
+ "name": "crelt",
+ "bytes": 611,
+ "pct": 0.032662635602058974
+ },
+ {
+ "name": "@codemirror/lang-json",
+ "bytes": 274,
+ "pct": 0.014647401235620555
+ }
+ ],
+ "topModules": [
+ {
+ "path": "src/generated/json-schema-validators.js",
+ "bytes": 214595,
+ "pct": 11.471748423934281,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "src/generated/example-dashboards.ts",
+ "bytes": 187686,
+ "pct": 10.033256015725108,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/view/dist/index.js",
+ "bytes": 186268,
+ "pct": 9.957453041447334,
+ "owner": "external",
+ "group": "@codemirror/view"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chart.js",
+ "bytes": 165504,
+ "pct": 8.84745800766476,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "src/generated/json-schemas.js",
+ "bytes": 55086,
+ "pct": 2.944769140384649,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/state/dist/index.js",
+ "bytes": 46747,
+ "pct": 2.498985640735599,
+ "owner": "external",
+ "group": "@codemirror/state"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/marked/lib/marked.esm.js",
+ "bytes": 41843,
+ "pct": 2.236829233219237,
+ "owner": "external",
+ "group": "marked"
+ },
+ {
+ "path": "src/ui/app.ts",
+ "bytes": 41314,
+ "pct": 2.2085501264541154,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@dagrejs/dagre/dist/dagre.esm.js",
+ "bytes": 40422,
+ "pct": 2.160865885935234,
+ "owner": "external",
+ "group": "@dagrejs/dagre"
+ },
+ {
+ "path": "src/ui/dashboard.ts",
+ "bytes": 35268,
+ "pct": 1.8853450612330866,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-sql/dist/index.js",
+ "bytes": 32138,
+ "pct": 1.718022558067056,
+ "owner": "external",
+ "group": "@codemirror/lang-sql"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chunks/helpers.dataset.js",
+ "bytes": 31641,
+ "pct": 1.6914540967017153,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/autocomplete/dist/index.js",
+ "bytes": 31522,
+ "pct": 1.6850926341212817,
+ "owner": "external",
+ "group": "@codemirror/autocomplete"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/lr/dist/index.js",
+ "bytes": 26552,
+ "pct": 1.419408020467872,
+ "owner": "external",
+ "group": "@lezer/lr"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/language/dist/index.js",
+ "bytes": 24486,
+ "pct": 1.3089644768445434,
+ "owner": "external",
+ "group": "@codemirror/language"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/commands/dist/index.js",
+ "bytes": 23371,
+ "pct": 1.2493591761959415,
+ "owner": "external",
+ "group": "@codemirror/commands"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/common/dist/index.js",
+ "bytes": 20388,
+ "pct": 1.089894950335153,
+ "owner": "external",
+ "group": "@lezer/common"
+ },
+ {
+ "path": "src/ui/results.ts",
+ "bytes": 18828,
+ "pct": 1.0065009870958534,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/search/dist/index.js",
+ "bytes": 18472,
+ "pct": 0.9874700570232953,
+ "owner": "external",
+ "group": "@codemirror/search"
+ },
+ {
+ "path": "src/dashboard/application/dashboard-viewer-session.ts",
+ "bytes": 15288,
+ "pct": 0.8172608397451353,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/dashboard-tree.ts",
+ "bytes": 13899,
+ "pct": 0.7430081378609127,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/file-menu.ts",
+ "bytes": 12919,
+ "pct": 0.6906196224926349,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/core/chart-data.ts",
+ "bytes": 12028,
+ "pct": 0.6429888396424965,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/explain-graph.ts",
+ "bytes": 11980,
+ "pct": 0.6404228715428257,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/state.ts",
+ "bytes": 11032,
+ "pct": 0.5897450015743284,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/shortcuts.ts",
+ "bytes": 10307,
+ "pct": 0.5509881917355514,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/net/ch-client.ts",
+ "bytes": 9860,
+ "pct": 0.5270926138073675,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/dashboard/model/workspace-semantics.ts",
+ "bytes": 8712,
+ "pct": 0.4657232100902419,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/xml/dist/index.js",
+ "bytes": 8569,
+ "pct": 0.4580787634599728,
+ "owner": "external",
+ "group": "@lezer/xml"
+ },
+ {
+ "path": "src/core/spec-schema.ts",
+ "bytes": 8272,
+ "pct": 0.44220183584325995,
+ "owner": "project",
+ "group": "src"
+ }
+ ],
+ "notes": [
+ "Percentages are of raw contributed output bytes (metafile bytesInOutput); gzip/Brotli are measured per whole artifact only — compression is not additive across modules."
+ ]
+}
\ No newline at end of file
diff --git a/docs/design/577/manifest-v2/s1/ui-complexity-report.json b/docs/design/577/manifest-v2/s1/ui-complexity-report.json
new file mode 100644
index 00000000..70925a86
--- /dev/null
+++ b/docs/design/577/manifest-v2/s1/ui-complexity-report.json
@@ -0,0 +1,355 @@
+{
+ "schemaVersion": 1,
+ "label": "s1",
+ "metricTiers": {
+ "deciding": {
+ "ownedProductionCode": {
+ "owner": "this instrument"
+ },
+ "parity": {
+ "owner": "tests/e2e/shell-parity.spec.js"
+ },
+ "artifact": {
+ "owner": "build/size-report.mjs"
+ },
+ "changeAmplification": {
+ "owner": "frozen controlled-change experiment"
+ },
+ "cleanupObligations": {
+ "owner": "leak invariant + manual obligation count"
+ }
+ },
+ "explanatory": {
+ "minifiedBytes": {
+ "demotedBecause": "ignores tree shaking, shared helpers, and complexity moved into a dependency"
+ },
+ "lcovBranches": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lcovFunctions": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lifecycleSites": {
+ "demotedBecause": "hidden/dataset/replaceChildren go syntactically invisible under a vDOM"
+ }
+ }
+ },
+ "environment": {
+ "commit": "7e7c8c4b6d8c5853d4e59dc599c5705f8e9cacab",
+ "commitDirty": "clean",
+ "node": "v25.9.0",
+ "esbuild": "0.28.1",
+ "platform": "darwin-arm64",
+ "packageLock": "sha256:9042ec0421903e38"
+ },
+ "ownedProductionCode": {
+ "plumbingNormalizedLines": 742,
+ "islandNormalizedLines": 203,
+ "domainNormalizedLines": 341
+ },
+ "byClass": {
+ "domain": {
+ "files": 4,
+ "absentFiles": 0,
+ "physicalLines": 1367,
+ "normalizedLines": 341,
+ "nonCodeLines": 953,
+ "minifiedBytes": 8984,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 4
+ },
+ "plumbing": {
+ "files": 5,
+ "absentFiles": 7,
+ "physicalLines": 1766,
+ "normalizedLines": 742,
+ "nonCodeLines": 949,
+ "minifiedBytes": 17444,
+ "lifecycle": {
+ "domMutation": 72,
+ "listener": 7,
+ "effect": 17,
+ "disposal": 36,
+ "focus": 8
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 5
+ },
+ "island": {
+ "files": 2,
+ "absentFiles": 0,
+ "physicalLines": 603,
+ "normalizedLines": 203,
+ "nonCodeLines": 359,
+ "minifiedBytes": 4269,
+ "lifecycle": {
+ "domMutation": 14,
+ "listener": 18,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 2
+ }
+ },
+ "overall": {
+ "files": 11,
+ "absentFiles": 7,
+ "physicalLines": 3736,
+ "normalizedLines": 1286,
+ "nonCodeLines": 2261,
+ "minifiedBytes": 30697,
+ "lifecycle": {
+ "domMutation": 96,
+ "listener": 25,
+ "effect": 21,
+ "disposal": 40,
+ "focus": 8
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 11
+ },
+ "files": [
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam",
+ "physicalLines": 175,
+ "sourceBlankLines": 8,
+ "normalizedLines": 48,
+ "minifiedBytes": 951,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free",
+ "physicalLines": 853,
+ "sourceBlankLines": 39,
+ "normalizedLines": 150,
+ "minifiedBytes": 4836,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching",
+ "physicalLines": 905,
+ "sourceBlankLines": 31,
+ "normalizedLines": 340,
+ "minifiedBytes": 7856,
+ "lifecycle": {
+ "domMutation": 41,
+ "listener": 3,
+ "effect": 14,
+ "disposal": 23,
+ "focus": 4
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes",
+ "physicalLines": 144,
+ "sourceBlankLines": 7,
+ "normalizedLines": 56,
+ "minifiedBytes": 993,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics",
+ "physicalLines": 443,
+ "sourceBlankLines": 29,
+ "normalizedLines": 157,
+ "minifiedBytes": 3170,
+ "lifecycle": {
+ "domMutation": 12,
+ "listener": 14,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation",
+ "physicalLines": 129,
+ "sourceBlankLines": 8,
+ "normalizedLines": 51,
+ "minifiedBytes": 775,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 1,
+ "disposal": 6,
+ "focus": 3
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations",
+ "physicalLines": 202,
+ "sourceBlankLines": 13,
+ "normalizedLines": 69,
+ "minifiedBytes": 1464,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)",
+ "physicalLines": 271,
+ "sourceBlankLines": 20,
+ "normalizedLines": 131,
+ "minifiedBytes": 2048,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 1,
+ "focus": 1
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/adopt.ts",
+ "class": "plumbing",
+ "sliceItem": "7 — the Preact/foreign-DOM boundary every imperative integration point crosses",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/focus-settlement.ts",
+ "class": "plumbing",
+ "sliceItem": "3 — capture/settle across a structural transition where the focused source disappears",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/right-inspector-view.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host (replaces right-inspector.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-context.types.ts",
+ "class": "plumbing",
+ "sliceItem": "1 — type-only seam contract for the view; erased by the build",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-host.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — the composition root: shell geometry below the header, desktop/mobile projection, Query/Dashboard host switching (replaces app-shell.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-layout.ts",
+ "class": "plumbing",
+ "sliceItem": "2, 3 — the left-navigation layout as derived state (replaces applyEffectiveLeftNavigationLayout)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/shell-view.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 2, 5, 6 — the rendered application frame, the left navigation in wide/rail/drawer presentations, and the mobile bottom nav (replaces app-shell.ts markup + left-rail.ts)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher",
+ "physicalLines": 137,
+ "sourceBlankLines": 13,
+ "normalizedLines": 74,
+ "minifiedBytes": 1733,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector",
+ "physicalLines": 160,
+ "sourceBlankLines": 12,
+ "normalizedLines": 46,
+ "minifiedBytes": 1099,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 4,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host",
+ "physicalLines": 317,
+ "sourceBlankLines": 9,
+ "normalizedLines": 164,
+ "minifiedBytes": 5772,
+ "lifecycle": {
+ "domMutation": 17,
+ "listener": 4,
+ "effect": 2,
+ "disposal": 6,
+ "focus": 0
+ },
+ "lcov": null
+ }
+ ]
+}
diff --git a/docs/design/577/manifest-v2/s1/ui-complexity-report.md b/docs/design/577/manifest-v2/s1/ui-complexity-report.md
new file mode 100644
index 00000000..731b98f2
--- /dev/null
+++ b/docs/design/577/manifest-v2/s1/ui-complexity-report.md
@@ -0,0 +1,61 @@
+# UI complexity report — s1
+
+Schema version 1. Counts run over esbuild-transformed
+source (comments stripped, formatting normalized), never raw text.
+
+**Deciding metric owned here:** `ownedProductionCode` — normalized lines by class.
+Every other number below is explanatory and must not be voted with:
+- `minifiedBytes` — ignores tree shaking, shared helpers, and complexity moved into a dependency
+- `lcovBranches` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lcovFunctions` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lifecycleSites` — hidden/dataset/replaceChildren go syntactically invisible under a vDOM
+
+## Owned production code (deciding)
+
+| Class | Files | Normalized (code) | Physical | Non-code |
+|---|---:|---:|---:|---:|
+| domain | 4 | **341** | 1367 | 953 |
+| plumbing | 5 | **742** | 1766 | 949 |
+| island | 2 | **203** | 603 | 359 |
+| **total** | **11** | **1286** | **3736** | **2261** |
+
+Physical LOC overstates code by **2261** non-code lines
+(comments plus formatting-only lines). A raw-LOC comparison would have credited
+those as complexity — which is the specific error this instrument exists to prevent.
+
+## Explanatory metrics
+
+| File | Class | Code | Minified B | DOM | Listeners | Effects | Disposal | focus() | lcov BRF/FNF |
+|---|---|---:|---:|---:|---:|---:|---:|---:|---|
+| `src/application/left-nav.ts` | domain | 48 | 951 | 4 | 0 | 2 | 0 | 0 | _unmatched_ |
+| `src/core/left-nav-layout.ts` | domain | 150 | 4836 | 0 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/app-shell.ts` | plumbing | 340 | 7856 | 41 | 3 | 14 | 23 | 4 | _unmatched_ |
+| `src/ui/drawer.ts` | plumbing | 56 | 993 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/left-nav-separator.ts` | island | 157 | 3170 | 12 | 14 | 2 | 4 | 0 | _unmatched_ |
+| `src/ui/left-rail.ts` | plumbing | 51 | 775 | 2 | 0 | 1 | 6 | 3 | _unmatched_ |
+| `src/ui/nav-sections.ts` | domain | 69 | 1464 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/right-inspector.ts` | plumbing | 131 | 2048 | 10 | 0 | 0 | 1 | 1 | _unmatched_ |
+| `src/ui/shell/adopt.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/focus-settlement.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/right-inspector-view.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-context.types.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-host.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-layout.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/shell-view.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/sidebar-upper.ts` | domain | 74 | 1733 | 4 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/splitters.ts` | island | 46 | 1099 | 2 | 4 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/workbench/workbench-shell.ts` | plumbing | 164 | 5772 | 17 | 4 | 2 | 6 | 0 | _unmatched_ |
+
+> 7 manifest file(s) absent in this state — measured against the one canonical
+> manifest so every state covers an identical file set (a deletion must show up, not vanish).
+
+> 11 file(s) had no lcov record — reported as unmatched, never as zero branches.
+
+## Environment
+
+- **commit**: `7e7c8c4b6d8c5853d4e59dc599c5705f8e9cacab`
+- **commitDirty**: `clean`
+- **node**: `v25.9.0`
+- **esbuild**: `0.28.1`
+- **platform**: `darwin-arm64`
+- **packageLock**: `sha256:9042ec0421903e38`
diff --git a/docs/design/577/manifest-v2/s2/bundle-size-report.json b/docs/design/577/manifest-v2/s2/bundle-size-report.json
new file mode 100644
index 00000000..2658bd48
--- /dev/null
+++ b/docs/design/577/manifest-v2/s2/bundle-size-report.json
@@ -0,0 +1,396 @@
+{
+ "schemaVersion": 1,
+ "artifact": {
+ "raw": 2149976,
+ "gzip": 632519,
+ "brotli": 535612
+ },
+ "js": {
+ "raw": 1890126,
+ "gzip": 517030,
+ "brotli": 426416
+ },
+ "css": {
+ "raw": 239794,
+ "gzip": 109847,
+ "brotli": 105969
+ },
+ "totalOutputBytes": 1889930,
+ "entryPoints": [
+ {
+ "file": "main.js",
+ "entryPoint": "src/main.ts",
+ "bytes": 1890123
+ }
+ ],
+ "ownership": {
+ "project": {
+ "bytes": 635169,
+ "pct": 33.60807014016392
+ },
+ "generated": {
+ "bytes": 457308,
+ "pct": 24.19708666458546
+ },
+ "external": {
+ "bytes": 797453,
+ "pct": 42.19484319525062
+ },
+ "other": {
+ "bytes": 0,
+ "pct": 0
+ }
+ },
+ "packages": [
+ {
+ "name": "chart.js",
+ "bytes": 197174,
+ "pct": 10.432873175196965
+ },
+ {
+ "name": "@codemirror/view",
+ "bytes": 186268,
+ "pct": 9.85581476562624
+ },
+ {
+ "name": "date-fns",
+ "bytes": 47998,
+ "pct": 2.5396707814575143
+ },
+ {
+ "name": "@codemirror/state",
+ "bytes": 46747,
+ "pct": 2.4734778536771205
+ },
+ {
+ "name": "marked",
+ "bytes": 41843,
+ "pct": 2.213997343816967
+ },
+ {
+ "name": "@dagrejs/dagre",
+ "bytes": 40422,
+ "pct": 2.1388093738921548
+ },
+ {
+ "name": "@codemirror/lang-sql",
+ "bytes": 32160,
+ "pct": 1.7016503256734377
+ },
+ {
+ "name": "@codemirror/autocomplete",
+ "bytes": 31522,
+ "pct": 1.6678924616255628
+ },
+ {
+ "name": "@lezer/lr",
+ "bytes": 26552,
+ "pct": 1.4049197589328706
+ },
+ {
+ "name": "@codemirror/language",
+ "bytes": 24521,
+ "pct": 1.297455461313382
+ },
+ {
+ "name": "@codemirror/commands",
+ "bytes": 23371,
+ "pct": 1.2366066468070247
+ },
+ {
+ "name": "@lezer/common",
+ "bytes": 20388,
+ "pct": 1.078770113178795
+ },
+ {
+ "name": "@codemirror/search",
+ "bytes": 18472,
+ "pct": 0.9773906970099422
+ },
+ {
+ "name": "preact",
+ "bytes": 12519,
+ "pct": 0.6624054859174678
+ },
+ {
+ "name": "@lezer/xml",
+ "bytes": 8583,
+ "pct": 0.4541438042678829
+ },
+ {
+ "name": "@kurkle/color",
+ "bytes": 7597,
+ "pct": 0.40197255983025826
+ },
+ {
+ "name": "@lezer/highlight",
+ "bytes": 7218,
+ "pct": 0.38191890704946746
+ },
+ {
+ "name": "@codemirror/lang-xml",
+ "bytes": 5975,
+ "pct": 0.31614927536998727
+ },
+ {
+ "name": "@preact/signals-core",
+ "bytes": 4749,
+ "pct": 0.2512791479049489
+ },
+ {
+ "name": "@preact/signals",
+ "bytes": 3140,
+ "pct": 0.16614371960866275
+ },
+ {
+ "name": "@marijn/find-cluster-break",
+ "bytes": 2332,
+ "pct": 0.12339081341636991
+ },
+ {
+ "name": "style-mod",
+ "bytes": 2202,
+ "pct": 0.11651225177652083
+ },
+ {
+ "name": "chartjs-adapter-date-fns",
+ "bytes": 1688,
+ "pct": 0.0893154772928098
+ },
+ {
+ "name": "@lezer/json",
+ "bytes": 1607,
+ "pct": 0.08502960427105766
+ },
+ {
+ "name": "w3c-keyname",
+ "bytes": 1520,
+ "pct": 0.08042625917362019
+ },
+ {
+ "name": "crelt",
+ "bytes": 611,
+ "pct": 0.03232923970729075
+ },
+ {
+ "name": "@codemirror/lang-json",
+ "bytes": 274,
+ "pct": 0.014497891456297323
+ }
+ ],
+ "topModules": [
+ {
+ "path": "src/generated/json-schema-validators.js",
+ "bytes": 214536,
+ "pct": 11.35153153820512,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "src/generated/example-dashboards.ts",
+ "bytes": 187686,
+ "pct": 9.93084399951321,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/view/dist/index.js",
+ "bytes": 186268,
+ "pct": 9.85581476562624,
+ "owner": "external",
+ "group": "@codemirror/view"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chart.js",
+ "bytes": 165504,
+ "pct": 8.757149735704498,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "src/generated/json-schemas.js",
+ "bytes": 55086,
+ "pct": 2.914711126867133,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/state/dist/index.js",
+ "bytes": 46747,
+ "pct": 2.4734778536771205,
+ "owner": "external",
+ "group": "@codemirror/state"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/marked/lib/marked.esm.js",
+ "bytes": 41843,
+ "pct": 2.213997343816967,
+ "owner": "external",
+ "group": "marked"
+ },
+ {
+ "path": "src/ui/app.ts",
+ "bytes": 41350,
+ "pct": 2.1879117215981547,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@dagrejs/dagre/dist/dagre.esm.js",
+ "bytes": 40422,
+ "pct": 2.1388093738921548,
+ "owner": "external",
+ "group": "@dagrejs/dagre"
+ },
+ {
+ "path": "src/ui/dashboard.ts",
+ "bytes": 35352,
+ "pct": 1.8705454699380402,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-sql/dist/index.js",
+ "bytes": 32160,
+ "pct": 1.7016503256734377,
+ "owner": "external",
+ "group": "@codemirror/lang-sql"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chunks/helpers.dataset.js",
+ "bytes": 31641,
+ "pct": 1.6741889911266554,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/autocomplete/dist/index.js",
+ "bytes": 31522,
+ "pct": 1.6678924616255628,
+ "owner": "external",
+ "group": "@codemirror/autocomplete"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/lr/dist/index.js",
+ "bytes": 26552,
+ "pct": 1.4049197589328706,
+ "owner": "external",
+ "group": "@lezer/lr"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/language/dist/index.js",
+ "bytes": 24521,
+ "pct": 1.297455461313382,
+ "owner": "external",
+ "group": "@codemirror/language"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/commands/dist/index.js",
+ "bytes": 23371,
+ "pct": 1.2366066468070247,
+ "owner": "external",
+ "group": "@codemirror/commands"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/common/dist/index.js",
+ "bytes": 20388,
+ "pct": 1.078770113178795,
+ "owner": "external",
+ "group": "@lezer/common"
+ },
+ {
+ "path": "src/ui/results.ts",
+ "bytes": 18812,
+ "pct": 0.9953807812987783,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/search/dist/index.js",
+ "bytes": 18472,
+ "pct": 0.9773906970099422,
+ "owner": "external",
+ "group": "@codemirror/search"
+ },
+ {
+ "path": "src/dashboard/application/dashboard-viewer-session.ts",
+ "bytes": 15281,
+ "pct": 0.808548464757954,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/dashboard-tree.ts",
+ "bytes": 13899,
+ "pct": 0.7354240633250967,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/file-menu.ts",
+ "bytes": 12919,
+ "pct": 0.6835702909631574,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/core/chart-data.ts",
+ "bytes": 12062,
+ "pct": 0.6382246961527676,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/explain-graph.ts",
+ "bytes": 11980,
+ "pct": 0.6338859111184012,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/state.ts",
+ "bytes": 11052,
+ "pct": 0.5847835634124016,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/preact/dist/preact.module.js",
+ "bytes": 10686,
+ "pct": 0.5654177667955956,
+ "owner": "external",
+ "group": "preact"
+ },
+ {
+ "path": "src/ui/shortcuts.ts",
+ "bytes": 10307,
+ "pct": 0.5453641140148048,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/net/ch-client.ts",
+ "bytes": 9860,
+ "pct": 0.5217124443762468,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/dashboard/model/workspace-semantics.ts",
+ "bytes": 8724,
+ "pct": 0.4616043980464885,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/xml/dist/index.js",
+ "bytes": 8583,
+ "pct": 0.4541438042678829,
+ "owner": "external",
+ "group": "@lezer/xml"
+ }
+ ],
+ "notes": [
+ "Percentages are of raw contributed output bytes (metafile bytesInOutput); gzip/Brotli are measured per whole artifact only — compression is not additive across modules."
+ ]
+}
\ No newline at end of file
diff --git a/docs/design/577/manifest-v2/s2/esbuild-inputs.json b/docs/design/577/manifest-v2/s2/esbuild-inputs.json
new file mode 100644
index 00000000..888ccf29
--- /dev/null
+++ b/docs/design/577/manifest-v2/s2/esbuild-inputs.json
@@ -0,0 +1,1077 @@
+{
+ "_comment": [
+ "Per-input attribution for the S2 artifact, extracted from the esbuild",
+ "metafile. This is the evidence that the vanilla shell is ABSENT from the",
+ "built artifact rather than shipped beside the Preact one — grep it for",
+ "app-shell.ts, left-rail.ts or right-inspector.ts and there is nothing to",
+ "find. Regenerate with: node build/size-report.mjs (writes esbuild-meta.json)."
+ ],
+ "output": "main.js",
+ "inputs": {
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@kurkle/color/dist/color.esm.js": {
+ "bytesInOutput": 7597
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chunks/helpers.dataset.js": {
+ "bytesInOutput": 31641
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/dist/chart.js": {
+ "bytesInOutput": 165504
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chart.js/auto/auto.js": {
+ "bytesInOutput": 29
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/constants.js": {
+ "bytesInOutput": 186
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/constructFrom.js": {
+ "bytesInOutput": 141
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/toDate.js": {
+ "bytesInOutput": 35
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addDays.js": {
+ "bytesInOutput": 102
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addMonths.js": {
+ "bytesInOutput": 246
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addMilliseconds.js": {
+ "bytesInOutput": 48
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addHours.js": {
+ "bytesInOutput": 39
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/defaultOptions.js": {
+ "bytesInOutput": 34
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfWeek.js": {
+ "bytesInOutput": 229
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfISOWeek.js": {
+ "bytesInOutput": 52
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getISOWeekYear.js": {
+ "bytesInOutput": 248
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/getTimezoneOffsetInMilliseconds.js": {
+ "bytesInOutput": 200
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/normalizeDates.js": {
+ "bytesInOutput": 89
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfDay.js": {
+ "bytesInOutput": 64
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInCalendarDays.js": {
+ "bytesInOutput": 108
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfISOWeekYear.js": {
+ "bytesInOutput": 102
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addMinutes.js": {
+ "bytesInOutput": 74
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addQuarters.js": {
+ "bytesInOutput": 38
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addSeconds.js": {
+ "bytesInOutput": 40
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addWeeks.js": {
+ "bytesInOutput": 38
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/addYears.js": {
+ "bytesInOutput": 39
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/compareAsc.js": {
+ "bytesInOutput": 59
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/isDate.js": {
+ "bytesInOutput": 113
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/isValid.js": {
+ "bytesInOutput": 66
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInCalendarMonths.js": {
+ "bytesInOutput": 118
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInCalendarYears.js": {
+ "bytesInOutput": 81
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInDays.js": {
+ "bytesInOutput": 407
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/getRoundingMethod.js": {
+ "bytesInOutput": 76
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInHours.js": {
+ "bytesInOutput": 87
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInMilliseconds.js": {
+ "bytesInOutput": 37
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInMinutes.js": {
+ "bytesInOutput": 68
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfDay.js": {
+ "bytesInOutput": 69
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfMonth.js": {
+ "bytesInOutput": 121
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/isLastDayOfMonth.js": {
+ "bytesInOutput": 60
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInMonths.js": {
+ "bytesInOutput": 254
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInQuarters.js": {
+ "bytesInOutput": 69
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInSeconds.js": {
+ "bytesInOutput": 69
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInWeeks.js": {
+ "bytesInOutput": 69
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/differenceInYears.js": {
+ "bytesInOutput": 159
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfQuarter.js": {
+ "bytesInOutput": 103
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfMonth.js": {
+ "bytesInOutput": 77
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfYear.js": {
+ "bytesInOutput": 110
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfYear.js": {
+ "bytesInOutput": 99
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfHour.js": {
+ "bytesInOutput": 68
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfWeek.js": {
+ "bytesInOutput": 239
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfMinute.js": {
+ "bytesInOutput": 65
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfQuarter.js": {
+ "bytesInOutput": 110
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/endOfSecond.js": {
+ "bytesInOutput": 67
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US/_lib/formatDistance.js": {
+ "bytesInOutput": 1089
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/_lib/buildFormatLongFn.js": {
+ "bytesInOutput": 123
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US/_lib/formatLong.js": {
+ "bytesInOutput": 423
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US/_lib/formatRelative.js": {
+ "bytesInOutput": 169
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/_lib/buildLocalizeFn.js": {
+ "bytesInOutput": 400
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US/_lib/localize.js": {
+ "bytesInOutput": 1994
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/_lib/buildMatchFn.js": {
+ "bytesInOutput": 556
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/_lib/buildMatchPatternFn.js": {
+ "bytesInOutput": 271
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US/_lib/match.js": {
+ "bytesInOutput": 1866
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/locale/en-US.js": {
+ "bytesInOutput": 142
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/defaultLocale.js": {
+ "bytesInOutput": 0
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getDayOfYear.js": {
+ "bytesInOutput": 56
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getISOWeek.js": {
+ "bytesInOutput": 77
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getWeekYear.js": {
+ "bytesInOutput": 376
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfWeekYear.js": {
+ "bytesInOutput": 251
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getWeek.js": {
+ "bytesInOutput": 81
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/addLeadingZeros.js": {
+ "bytesInOutput": 86
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/format/lightFormatters.js": {
+ "bytesInOutput": 642
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/format/formatters.js": {
+ "bytesInOutput": 6951
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/format/longFormatters.js": {
+ "bytesInOutput": 718
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/_lib/protectedTokens.js": {
+ "bytesInOutput": 461
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/format.js": {
+ "bytesInOutput": 1303
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getDefaultOptions.js": {
+ "bytesInOutput": 44
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/getISODay.js": {
+ "bytesInOutput": 61
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/transpose.js": {
+ "bytesInOutput": 259
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/Setter.js": {
+ "bytesInOutput": 514
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/Parser.js": {
+ "bytesInOutput": 184
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/EraParser.js": {
+ "bytesInOutput": 458
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/constants.js": {
+ "bytesInOutput": 739
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/utils.js": {
+ "bytesInOutput": 1246
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/YearParser.js": {
+ "bytesInOutput": 609
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/LocalWeekYearParser.js": {
+ "bytesInOutput": 671
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ISOWeekYearParser.js": {
+ "bytesInOutput": 302
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ExtendedYearParser.js": {
+ "bytesInOutput": 268
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/QuarterParser.js": {
+ "bytesInOutput": 747
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/StandAloneQuarterParser.js": {
+ "bytesInOutput": 747
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/MonthParser.js": {
+ "bytesInOutput": 770
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/StandAloneMonthParser.js": {
+ "bytesInOutput": 770
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/setWeek.js": {
+ "bytesInOutput": 95
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/LocalWeekParser.js": {
+ "bytesInOutput": 367
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/setISOWeek.js": {
+ "bytesInOutput": 85
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ISOWeekParser.js": {
+ "bytesInOutput": 365
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/DateParser.js": {
+ "bytesInOutput": 550
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/DayOfYearParser.js": {
+ "bytesInOutput": 474
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/setDay.js": {
+ "bytesInOutput": 233
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/DayParser.js": {
+ "bytesInOutput": 816
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/LocalDayParser.js": {
+ "bytesInOutput": 1006
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/StandAloneLocalDayParser.js": {
+ "bytesInOutput": 1006
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/setISODay.js": {
+ "bytesInOutput": 70
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ISODayParser.js": {
+ "bytesInOutput": 961
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/AMPMParser.js": {
+ "bytesInOutput": 602
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/AMPMMidnightParser.js": {
+ "bytesInOutput": 602
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/DayPeriodParser.js": {
+ "bytesInOutput": 594
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/Hour1to12Parser.js": {
+ "bytesInOutput": 426
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/Hour0to23Parser.js": {
+ "bytesInOutput": 349
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/Hour0To11Parser.js": {
+ "bytesInOutput": 387
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/Hour1To24Parser.js": {
+ "bytesInOutput": 368
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/MinuteParser.js": {
+ "bytesInOutput": 330
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/SecondParser.js": {
+ "bytesInOutput": 328
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/FractionOfSecondParser.js": {
+ "bytesInOutput": 249
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ISOTimezoneWithZParser.js": {
+ "bytesInOutput": 419
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/ISOTimezoneParser.js": {
+ "bytesInOutput": 419
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/TimestampSecondsParser.js": {
+ "bytesInOutput": 188
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers/TimestampMillisecondsParser.js": {
+ "bytesInOutput": 184
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse/_lib/parsers.js": {
+ "bytesInOutput": 288
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parse.js": {
+ "bytesInOutput": 1940
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfHour.js": {
+ "bytesInOutput": 64
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfMinute.js": {
+ "bytesInOutput": 62
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/startOfSecond.js": {
+ "bytesInOutput": 65
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/date-fns/parseISO.js": {
+ "bytesInOutput": 2610
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.esm.js": {
+ "bytesInOutput": 1688
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@dagrejs/dagre/dist/dagre.esm.js": {
+ "bytesInOutput": 40422
+ },
+ "src/ui/dom.ts": {
+ "bytesInOutput": 1475
+ },
+ "src/ui/brand-logo.ts": {
+ "bytesInOutput": 3398
+ },
+ "src/ui/icons.ts": {
+ "bytesInOutput": 6678
+ },
+ "src/core/quoted-span.ts": {
+ "bytesInOutput": 183
+ },
+ "src/core/sql-spans.ts": {
+ "bytesInOutput": 1111
+ },
+ "src/core/sql-split.ts": {
+ "bytesInOutput": 874
+ },
+ "src/core/format.ts": {
+ "bytesInOutput": 3359
+ },
+ "src/core/saved-query.ts": {
+ "bytesInOutput": 2843
+ },
+ "src/generated/json-schemas.js": {
+ "bytesInOutput": 55086
+ },
+ "src/generated/json-schema-validators.js": {
+ "bytesInOutput": 214536
+ },
+ "src/core/json-schema-validation.ts": {
+ "bytesInOutput": 4983
+ },
+ "src/core/library-migrations.ts": {
+ "bytesInOutput": 1537
+ },
+ "src/core/param-scan.ts": {
+ "bytesInOutput": 512
+ },
+ "src/core/clickhouse-type.ts": {
+ "bytesInOutput": 3931
+ },
+ "src/core/param-type.ts": {
+ "bytesInOutput": 2278
+ },
+ "src/core/param-validate.ts": {
+ "bytesInOutput": 2561
+ },
+ "src/core/param-serialize.ts": {
+ "bytesInOutput": 1400
+ },
+ "src/core/optional-blocks.ts": {
+ "bytesInOutput": 2464
+ },
+ "src/core/relative-time.ts": {
+ "bytesInOutput": 4942
+ },
+ "src/core/param-pipeline.ts": {
+ "bytesInOutput": 4475
+ },
+ "src/core/query-time-range.ts": {
+ "bytesInOutput": 1471
+ },
+ "src/core/library-codec.ts": {
+ "bytesInOutput": 4510
+ },
+ "src/core/storage.ts": {
+ "bytesInOutput": 420
+ },
+ "src/core/recent-values.ts": {
+ "bytesInOutput": 1257
+ },
+ "src/core/spec-schema.ts": {
+ "bytesInOutput": 8272
+ },
+ "src/core/spec-draft.ts": {
+ "bytesInOutput": 3111
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@preact/signals-core/dist/signals-core.module.js": {
+ "bytesInOutput": 4749
+ },
+ "src/dashboard/model/portable-limits.ts": {
+ "bytesInOutput": 341
+ },
+ "src/dashboard/model/workspace-diagnostics.ts": {
+ "bytesInOutput": 718
+ },
+ "src/dashboard/model/json-limits.ts": {
+ "bytesInOutput": 973
+ },
+ "src/dashboard/model/canonical-json.ts": {
+ "bytesInOutput": 2504
+ },
+ "src/dashboard/model/query-ownership.ts": {
+ "bytesInOutput": 632
+ },
+ "src/dashboard/model/workspace-semantics.ts": {
+ "bytesInOutput": 8724
+ },
+ "src/dashboard/model/dashboard-document.ts": {
+ "bytesInOutput": 1981
+ },
+ "src/workspace/stored-workspace-ownership.ts": {
+ "bytesInOutput": 1369
+ },
+ "src/workspace/stored-workspace.ts": {
+ "bytesInOutput": 3014
+ },
+ "src/workspace/workspace-sync.ts": {
+ "bytesInOutput": 550
+ },
+ "src/core/workspace-key.ts": {
+ "bytesInOutput": 371
+ },
+ "src/core/left-nav-layout.ts": {
+ "bytesInOutput": 3123
+ },
+ "src/state.ts": {
+ "bytesInOutput": 11052
+ },
+ "src/workspace/workspace-dashboards.ts": {
+ "bytesInOutput": 961
+ },
+ "src/core/export.ts": {
+ "bytesInOutput": 1609
+ },
+ "src/core/stream.ts": {
+ "bytesInOutput": 1674
+ },
+ "src/core/field-config.ts": {
+ "bytesInOutput": 927
+ },
+ "src/core/chart-data.ts": {
+ "bytesInOutput": 12062
+ },
+ "src/core/logs.ts": {
+ "bytesInOutput": 1717
+ },
+ "src/core/kpi.ts": {
+ "bytesInOutput": 4242
+ },
+ "src/core/panel-cfg.ts": {
+ "bytesInOutput": 3720
+ },
+ "src/core/sql-lex.ts": {
+ "bytesInOutput": 1443
+ },
+ "src/core/schema-graph.ts": {
+ "bytesInOutput": 4642
+ },
+ "src/net/ch-client.ts": {
+ "bytesInOutput": 9860
+ },
+ "src/editor/editor-port.ts": {
+ "bytesInOutput": 247
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@marijn/find-cluster-break/src/index.js": {
+ "bytesInOutput": 2332
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/state/dist/index.js": {
+ "bytesInOutput": 46747
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/style-mod/src/style-mod.js": {
+ "bytesInOutput": 2202
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/w3c-keyname/index.js": {
+ "bytesInOutput": 1520
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/crelt/index.js": {
+ "bytesInOutput": 611
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/view/dist/index.js": {
+ "bytesInOutput": 186268
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/common/dist/index.js": {
+ "bytesInOutput": 20388
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/highlight/dist/index.js": {
+ "bytesInOutput": 7218
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/language/dist/index.js": {
+ "bytesInOutput": 24521
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/commands/dist/index.js": {
+ "bytesInOutput": 23371
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/autocomplete/dist/index.js": {
+ "bytesInOutput": 31522
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/lr/dist/index.js": {
+ "bytesInOutput": 26552
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/json/dist/index.js": {
+ "bytesInOutput": 1607
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-json/dist/index.js": {
+ "bytesInOutput": 274
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/search/dist/index.js": {
+ "bytesInOutput": 18472
+ },
+ "src/editor/codemirror-base.ts": {
+ "bytesInOutput": 658
+ },
+ "src/core/spec-completion.ts": {
+ "bytesInOutput": 6416
+ },
+ "src/editor/spec-json-context.ts": {
+ "bytesInOutput": 5765
+ },
+ "src/editor/spec-completion-adapter.ts": {
+ "bytesInOutput": 2770
+ },
+ "src/editor/spec-editor.ts": {
+ "bytesInOutput": 4129
+ },
+ "src/ui/menu.ts": {
+ "bytesInOutput": 2370
+ },
+ "src/ui/confirm-menu.ts": {
+ "bytesInOutput": 418
+ },
+ "src/dashboard/model/tab-origin-badges.ts": {
+ "bytesInOutput": 2539
+ },
+ "src/ui/tabs.ts": {
+ "bytesInOutput": 4447
+ },
+ "src/application/dashboard-variable-config.ts": {
+ "bytesInOutput": 284
+ },
+ "src/core/dashboard-tree-ui-state.ts": {
+ "bytesInOutput": 1434
+ },
+ "src/core/dashboard-variables.ts": {
+ "bytesInOutput": 1307
+ },
+ "src/application/dashboard-tree-model.ts": {
+ "bytesInOutput": 7569
+ },
+ "src/application/left-nav.ts": {
+ "bytesInOutput": 684
+ },
+ "src/ui/placeholder.ts": {
+ "bytesInOutput": 216
+ },
+ "src/core/cell.ts": {
+ "bytesInOutput": 534
+ },
+ "src/core/doc-capability.ts": {
+ "bytesInOutput": 8016
+ },
+ "src/core/doc-documentation.ts": {
+ "bytesInOutput": 2458
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/marked/lib/marked.esm.js": {
+ "bytesInOutput": 41843
+ },
+ "src/core/doc-markdown.ts": {
+ "bytesInOutput": 5830
+ },
+ "src/ui/doc-markdown-view.ts": {
+ "bytesInOutput": 2277
+ },
+ "src/ui/chart-render.ts": {
+ "bytesInOutput": 2464
+ },
+ "src/core/sort.ts": {
+ "bytesInOutput": 290
+ },
+ "src/ui/grid-render.ts": {
+ "bytesInOutput": 2677
+ },
+ "src/ui/logs.ts": {
+ "bytesInOutput": 1084
+ },
+ "src/ui/kpi-panel.ts": {
+ "bytesInOutput": 1737
+ },
+ "src/core/result-choice.ts": {
+ "bytesInOutput": 598
+ },
+ "src/ui/panels.ts": {
+ "bytesInOutput": 6329
+ },
+ "src/core/explain.ts": {
+ "bytesInOutput": 1582
+ },
+ "src/core/script-result.ts": {
+ "bytesInOutput": 396
+ },
+ "src/core/dot.ts": {
+ "bytesInOutput": 585
+ },
+ "src/core/dot-layout.ts": {
+ "bytesInOutput": 1666
+ },
+ "src/core/type-display.ts": {
+ "bytesInOutput": 2022
+ },
+ "src/core/schema-cards.ts": {
+ "bytesInOutput": 1623
+ },
+ "src/core/panzoom.ts": {
+ "bytesInOutput": 439
+ },
+ "src/core/graph-layout.ts": {
+ "bytesInOutput": 868
+ },
+ "src/ui/toast.ts": {
+ "bytesInOutput": 728
+ },
+ "src/ui/schema-detail.ts": {
+ "bytesInOutput": 4600
+ },
+ "src/ui/detached-view.ts": {
+ "bytesInOutput": 1578
+ },
+ "src/ui/explain-graph.ts": {
+ "bytesInOutput": 11980
+ },
+ "src/core/variable-width.ts": {
+ "bytesInOutput": 409
+ },
+ "src/ui/var-field.ts": {
+ "bytesInOutput": 365
+ },
+ "src/ui/combobox.ts": {
+ "bytesInOutput": 2455
+ },
+ "src/ui/combo-footer.ts": {
+ "bytesInOutput": 448
+ },
+ "src/ui/relative-time-field.ts": {
+ "bytesInOutput": 2962
+ },
+ "src/ui/recent-field.ts": {
+ "bytesInOutput": 1028
+ },
+ "src/ui/enum-field.ts": {
+ "bytesInOutput": 1596
+ },
+ "src/ui/popover.ts": {
+ "bytesInOutput": 1570
+ },
+ "src/core/time-range.ts": {
+ "bytesInOutput": 4932
+ },
+ "src/ui/time-range-field.ts": {
+ "bytesInOutput": 4692
+ },
+ "src/ui/variable-option-field.ts": {
+ "bytesInOutput": 1800
+ },
+ "src/core/variable-selection.ts": {
+ "bytesInOutput": 391
+ },
+ "src/ui/multi-select-field.ts": {
+ "bytesInOutput": 3558
+ },
+ "src/ui/variable-bar.ts": {
+ "bytesInOutput": 6502
+ },
+ "src/ui/splitters.ts": {
+ "bytesInOutput": 984
+ },
+ "src/ui/drawer.ts": {
+ "bytesInOutput": 790
+ },
+ "src/core/panel-execution.ts": {
+ "bytesInOutput": 439
+ },
+ "src/ui/results.ts": {
+ "bytesInOutput": 18812
+ },
+ "src/core/connection-lifecycle.ts": {
+ "bytesInOutput": 2141
+ },
+ "src/ui/dialog-shell.ts": {
+ "bytesInOutput": 3346
+ },
+ "src/core/file-menu-model.ts": {
+ "bytesInOutput": 1707
+ },
+ "src/ui/dnd-mime.ts": {
+ "bytesInOutput": 178
+ },
+ "src/core/library-drag.ts": {
+ "bytesInOutput": 369
+ },
+ "src/core/tree-click-arbiter.ts": {
+ "bytesInOutput": 327
+ },
+ "src/application/dashboard-title.ts": {
+ "bytesInOutput": 284
+ },
+ "src/core/dashboard.ts": {
+ "bytesInOutput": 167
+ },
+ "src/dashboard/layouts/flow-layout.ts": {
+ "bytesInOutput": 2642
+ },
+ "src/dashboard/layouts/grafana-grid-layout.ts": {
+ "bytesInOutput": 5707
+ },
+ "src/dashboard/layouts/layout-registry.ts": {
+ "bytesInOutput": 1100
+ },
+ "src/dashboard/application/tile-membership.ts": {
+ "bytesInOutput": 218
+ },
+ "src/dashboard/application/dashboard-removal.ts": {
+ "bytesInOutput": 1505
+ },
+ "src/application/dashboard-delete.ts": {
+ "bytesInOutput": 1385
+ },
+ "src/application/dashboard-panel-metadata.ts": {
+ "bytesInOutput": 662
+ },
+ "src/dashboard/application/dashboard-commands.ts": {
+ "bytesInOutput": 5413
+ },
+ "src/dashboard/application/dashboard-query-resolver.ts": {
+ "bytesInOutput": 352
+ },
+ "src/dashboard/application/panel-creation.ts": {
+ "bytesInOutput": 812
+ },
+ "src/application/panel-creation-service.ts": {
+ "bytesInOutput": 883
+ },
+ "src/dashboard/application/library-assignment.ts": {
+ "bytesInOutput": 1464
+ },
+ "src/application/library-assignment-service.ts": {
+ "bytesInOutput": 2007
+ },
+ "src/ui/dashboard-tree.ts": {
+ "bytesInOutput": 13899
+ },
+ "src/ui/library-assign-menu.ts": {
+ "bytesInOutput": 2166
+ },
+ "src/ui/nav-sections.ts": {
+ "bytesInOutput": 1175
+ },
+ "src/ui/saved-history.ts": {
+ "bytesInOutput": 6841
+ },
+ "src/core/saved-io.ts": {
+ "bytesInOutput": 617
+ },
+ "src/dashboard/model/portable-bundle-codec.ts": {
+ "bytesInOutput": 2851
+ },
+ "src/dashboard/model/legacy-bundle.ts": {
+ "bytesInOutput": 561
+ },
+ "src/dashboard/model/bundle-order.ts": {
+ "bytesInOutput": 244
+ },
+ "src/dashboard/model/dashboard-export.ts": {
+ "bytesInOutput": 327
+ },
+ "src/workspace/workspace-operations.ts": {
+ "bytesInOutput": 412
+ },
+ "src/workspace/import-planner.ts": {
+ "bytesInOutput": 3673
+ },
+ "src/dashboard/application/empty-dashboard.ts": {
+ "bytesInOutput": 190
+ },
+ "src/application/dashboard-create.ts": {
+ "bytesInOutput": 277
+ },
+ "src/generated/example-dashboards.ts": {
+ "bytesInOutput": 187686
+ },
+ "src/ui/file-menu.ts": {
+ "bytesInOutput": 12919
+ },
+ "src/ui/app-header.ts": {
+ "bytesInOutput": 2001
+ },
+ "src/core/tile-reorder.ts": {
+ "bytesInOutput": 431
+ },
+ "src/core/dashboard-autoscroll.ts": {
+ "bytesInOutput": 837
+ },
+ "src/ui/dashboard-chart-interaction.ts": {
+ "bytesInOutput": 5559
+ },
+ "src/core/variable-options.ts": {
+ "bytesInOutput": 3298
+ },
+ "src/dashboard/model/json-merge-patch.ts": {
+ "bytesInOutput": 253
+ },
+ "src/dashboard/model/presentation-resolver.ts": {
+ "bytesInOutput": 2230
+ },
+ "src/dashboard/application/dashboard-viewer-session.ts": {
+ "bytesInOutput": 15281
+ },
+ "src/dashboard/application/panel-widen.ts": {
+ "bytesInOutput": 496
+ },
+ "src/dashboard/application/panel-tile-actions.ts": {
+ "bytesInOutput": 1563
+ },
+ "src/dashboard/application/panel-duplication.ts": {
+ "bytesInOutput": 796
+ },
+ "src/application/dashboard-panel-duplicate.ts": {
+ "bytesInOutput": 1094
+ },
+ "src/application/main-surface.ts": {
+ "bytesInOutput": 2166
+ },
+ "src/dashboard/model/dashboard-variable-store.ts": {
+ "bytesInOutput": 685
+ },
+ "src/ui/dashboard.ts": {
+ "bytesInOutput": 35352
+ },
+ "src/ui/theme-toggle.ts": {
+ "bytesInOutput": 188
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-sql/dist/index.js": {
+ "bytesInOutput": 32160
+ },
+ "src/editor/ch-lang.ts": {
+ "bytesInOutput": 344
+ },
+ "src/ui/doc-pane.ts": {
+ "bytesInOutput": 7280
+ },
+ "src/core/param-comparison.ts": {
+ "bytesInOutput": 1451
+ },
+ "src/ui/login.ts": {
+ "bytesInOutput": 7493
+ },
+ "src/ui/shortcuts.ts": {
+ "bytesInOutput": 10307
+ },
+ "src/application/query-execution-service.ts": {
+ "bytesInOutput": 2184
+ },
+ "src/core/jwt.ts": {
+ "bytesInOutput": 277
+ },
+ "src/core/pkce.ts": {
+ "bytesInOutput": 474
+ },
+ "src/core/target.ts": {
+ "bytesInOutput": 177
+ },
+ "src/net/oauth.ts": {
+ "bytesInOutput": 1366
+ },
+ "src/net/oauth-config.ts": {
+ "bytesInOutput": 2003
+ },
+ "src/application/connection-session.ts": {
+ "bytesInOutput": 7117
+ },
+ "src/core/oauth-document-recovery.ts": {
+ "bytesInOutput": 4980
+ },
+ "src/application/oauth-document-recovery-session.ts": {
+ "bytesInOutput": 4494
+ },
+ "src/application/authenticated-execution-scope.ts": {
+ "bytesInOutput": 748
+ },
+ "src/core/sql-reference.ts": {
+ "bytesInOutput": 1110
+ },
+ "src/core/completions.ts": {
+ "bytesInOutput": 4174
+ },
+ "src/application/schema-catalog-service.ts": {
+ "bytesInOutput": 7232
+ },
+ "src/core/from-scope.ts": {
+ "bytesInOutput": 2208
+ },
+ "src/application/workbench-parameter-session.ts": {
+ "bytesInOutput": 1985
+ },
+ "src/application/ch-session-params.ts": {
+ "bytesInOutput": 286
+ },
+ "src/application/export-service.ts": {
+ "bytesInOutput": 6158
+ },
+ "src/application/schema-graph-session.ts": {
+ "bytesInOutput": 3425
+ },
+ "src/application/app-preferences.ts": {
+ "bytesInOutput": 189
+ },
+ "src/workspace/workspace-repository.ts": {
+ "bytesInOutput": 3459
+ },
+ "src/workspace/indexeddb-workspace-store.ts": {
+ "bytesInOutput": 2930
+ },
+ "src/ui/conflict-resolution.ts": {
+ "bytesInOutput": 732
+ },
+ "src/core/sql-route.ts": {
+ "bytesInOutput": 772
+ },
+ "src/core/query-source.ts": {
+ "bytesInOutput": 236
+ },
+ "src/ui/workbench/workbench-session.ts": {
+ "bytesInOutput": 6168
+ },
+ "src/application/query-document-session.ts": {
+ "bytesInOutput": 1208
+ },
+ "src/core/share.ts": {
+ "bytesInOutput": 961
+ },
+ "src/application/saved-query-service.ts": {
+ "bytesInOutput": 1137
+ },
+ "src/ui/workbench/workbench-shell.ts": {
+ "bytesInOutput": 5368
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/preact/dist/preact.module.js": {
+ "bytesInOutput": 10686
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/preact/hooks/dist/hooks.module.js": {
+ "bytesInOutput": 1833
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@preact/signals/dist/signals.module.js": {
+ "bytesInOutput": 3140
+ },
+ "src/ui/schema.ts": {
+ "bytesInOutput": 4408
+ },
+ "src/ui/sidebar-upper.ts": {
+ "bytesInOutput": 1393
+ },
+ "src/ui/left-nav-separator.ts": {
+ "bytesInOutput": 2678
+ },
+ "src/ui/shell/shell-layout.ts": {
+ "bytesInOutput": 431
+ },
+ "src/ui/shell/focus-settlement.ts": {
+ "bytesInOutput": 264
+ },
+ "src/ui/shell/adopt.ts": {
+ "bytesInOutput": 211
+ },
+ "src/ui/shell/right-inspector-view.ts": {
+ "bytesInOutput": 2440
+ },
+ "src/ui/shell/shell-view.ts": {
+ "bytesInOutput": 3262
+ },
+ "src/ui/shell/shell-host.ts": {
+ "bytesInOutput": 5822
+ },
+ "src/ui/app.ts": {
+ "bytesInOutput": 41350
+ },
+ "src/core/doc-context.ts": {
+ "bytesInOutput": 6944
+ },
+ "src/editor/codemirror-adapter.ts": {
+ "bytesInOutput": 7363
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@lezer/xml/dist/index.js": {
+ "bytesInOutput": 8583
+ },
+ "../../../../Workspaces/altinity/altinity-sql-browser/node_modules/@codemirror/lang-xml/dist/index.js": {
+ "bytesInOutput": 5975
+ },
+ "src/editor/code-viewer.ts": {
+ "bytesInOutput": 875
+ },
+ "src/main.ts": {
+ "bytesInOutput": 2643
+ }
+ }
+}
diff --git a/docs/design/577/manifest-v2/s2/ui-complexity-report.json b/docs/design/577/manifest-v2/s2/ui-complexity-report.json
new file mode 100644
index 00000000..1b98719e
--- /dev/null
+++ b/docs/design/577/manifest-v2/s2/ui-complexity-report.json
@@ -0,0 +1,399 @@
+{
+ "schemaVersion": 1,
+ "label": "s2",
+ "metricTiers": {
+ "deciding": {
+ "ownedProductionCode": {
+ "owner": "this instrument"
+ },
+ "parity": {
+ "owner": "tests/e2e/shell-parity.spec.js"
+ },
+ "artifact": {
+ "owner": "build/size-report.mjs"
+ },
+ "changeAmplification": {
+ "owner": "frozen controlled-change experiment"
+ },
+ "cleanupObligations": {
+ "owner": "leak invariant + manual obligation count"
+ }
+ },
+ "explanatory": {
+ "minifiedBytes": {
+ "demotedBecause": "ignores tree shaking, shared helpers, and complexity moved into a dependency"
+ },
+ "lcovBranches": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lcovFunctions": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lifecycleSites": {
+ "demotedBecause": "hidden/dataset/replaceChildren go syntactically invisible under a vDOM"
+ }
+ }
+ },
+ "environment": {
+ "commit": "3f611b5e3a2566dd56d73170239b8ff116fc834e",
+ "commitDirty": "clean",
+ "node": "v25.9.0",
+ "esbuild": "0.28.1",
+ "platform": "darwin-arm64",
+ "packageLock": "sha256:5dd460cf3b874b6c"
+ },
+ "ownedProductionCode": {
+ "plumbingNormalizedLines": 1072,
+ "islandNormalizedLines": 203,
+ "domainNormalizedLines": 341
+ },
+ "byClass": {
+ "domain": {
+ "files": 4,
+ "absentFiles": 0,
+ "physicalLines": 1367,
+ "normalizedLines": 341,
+ "nonCodeLines": 953,
+ "minifiedBytes": 8984,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 4
+ },
+ "plumbing": {
+ "files": 9,
+ "absentFiles": 3,
+ "physicalLines": 2091,
+ "normalizedLines": 1072,
+ "nonCodeLines": 920,
+ "minifiedBytes": 21747,
+ "lifecycle": {
+ "domMutation": 46,
+ "listener": 6,
+ "effect": 21,
+ "disposal": 30,
+ "focus": 5
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 9
+ },
+ "island": {
+ "files": 2,
+ "absentFiles": 0,
+ "physicalLines": 603,
+ "normalizedLines": 203,
+ "nonCodeLines": 359,
+ "minifiedBytes": 4269,
+ "lifecycle": {
+ "domMutation": 14,
+ "listener": 18,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 2
+ }
+ },
+ "overall": {
+ "files": 15,
+ "absentFiles": 3,
+ "physicalLines": 4061,
+ "normalizedLines": 1616,
+ "nonCodeLines": 2232,
+ "minifiedBytes": 35000,
+ "lifecycle": {
+ "domMutation": 70,
+ "listener": 24,
+ "effect": 25,
+ "disposal": 34,
+ "focus": 5
+ },
+ "lcovBranchesFound": 0,
+ "lcovFunctionsFound": 0,
+ "lcovUnmatchedFiles": 15
+ },
+ "files": [
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam",
+ "physicalLines": 175,
+ "sourceBlankLines": 8,
+ "normalizedLines": 48,
+ "minifiedBytes": 951,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free",
+ "physicalLines": 853,
+ "sourceBlankLines": 39,
+ "normalizedLines": 150,
+ "minifiedBytes": 4836,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching",
+ "absent": true
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes",
+ "physicalLines": 144,
+ "sourceBlankLines": 7,
+ "normalizedLines": 56,
+ "minifiedBytes": 993,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics",
+ "physicalLines": 443,
+ "sourceBlankLines": 29,
+ "normalizedLines": 157,
+ "minifiedBytes": 3170,
+ "lifecycle": {
+ "domMutation": 12,
+ "listener": 14,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation",
+ "absent": true
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations",
+ "physicalLines": 202,
+ "sourceBlankLines": 13,
+ "normalizedLines": 69,
+ "minifiedBytes": 1464,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/shell/adopt.ts",
+ "class": "plumbing",
+ "sliceItem": "7 — the Preact/foreign-DOM boundary every imperative integration point crosses",
+ "physicalLines": 69,
+ "sourceBlankLines": 3,
+ "normalizedLines": 17,
+ "minifiedBytes": 237,
+ "lifecycle": {
+ "domMutation": 1,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/focus-settlement.ts",
+ "class": "plumbing",
+ "sliceItem": "3 — capture/settle across a structural transition where the focused source disappears",
+ "physicalLines": 123,
+ "sourceBlankLines": 5,
+ "normalizedLines": 21,
+ "minifiedBytes": 294,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 1
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/right-inspector-view.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host (replaces right-inspector.ts)",
+ "physicalLines": 315,
+ "sourceBlankLines": 16,
+ "normalizedLines": 192,
+ "minifiedBytes": 2884,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 1,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/shell-context.types.ts",
+ "class": "plumbing",
+ "sliceItem": "1 — type-only seam contract for the view; erased by the build",
+ "physicalLines": 71,
+ "sourceBlankLines": 5,
+ "normalizedLines": 0,
+ "minifiedBytes": 0,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/shell-host.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — the composition root: shell geometry below the header, desktop/mobile projection, Query/Dashboard host switching (replaces app-shell.ts)",
+ "physicalLines": 636,
+ "sourceBlankLines": 36,
+ "normalizedLines": 391,
+ "minifiedBytes": 7169,
+ "lifecycle": {
+ "domMutation": 21,
+ "listener": 2,
+ "effect": 17,
+ "disposal": 23,
+ "focus": 3
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/shell-layout.ts",
+ "class": "plumbing",
+ "sliceItem": "2, 3 — the left-navigation layout as derived state (replaces applyEffectiveLeftNavigationLayout)",
+ "physicalLines": 167,
+ "sourceBlankLines": 9,
+ "normalizedLines": 39,
+ "minifiedBytes": 716,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/shell/shell-view.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 2, 5, 6 — the rendered application frame, the left navigation in wide/rail/drawer presentations, and the mobile bottom nav (replaces app-shell.ts markup + left-rail.ts)",
+ "physicalLines": 249,
+ "sourceBlankLines": 9,
+ "normalizedLines": 192,
+ "minifiedBytes": 3682,
+ "lifecycle": {
+ "domMutation": 1,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 1
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher",
+ "physicalLines": 137,
+ "sourceBlankLines": 13,
+ "normalizedLines": 74,
+ "minifiedBytes": 1733,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector",
+ "physicalLines": 160,
+ "sourceBlankLines": 12,
+ "normalizedLines": 46,
+ "minifiedBytes": 1099,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 4,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host",
+ "physicalLines": 317,
+ "sourceBlankLines": 9,
+ "normalizedLines": 164,
+ "minifiedBytes": 5772,
+ "lifecycle": {
+ "domMutation": 17,
+ "listener": 4,
+ "effect": 2,
+ "disposal": 6,
+ "focus": 0
+ },
+ "lcov": null
+ }
+ ]
+}
diff --git a/docs/design/577/manifest-v2/s2/ui-complexity-report.md b/docs/design/577/manifest-v2/s2/ui-complexity-report.md
new file mode 100644
index 00000000..09aefb7c
--- /dev/null
+++ b/docs/design/577/manifest-v2/s2/ui-complexity-report.md
@@ -0,0 +1,61 @@
+# UI complexity report — s2
+
+Schema version 1. Counts run over esbuild-transformed
+source (comments stripped, formatting normalized), never raw text.
+
+**Deciding metric owned here:** `ownedProductionCode` — normalized lines by class.
+Every other number below is explanatory and must not be voted with:
+- `minifiedBytes` — ignores tree shaking, shared helpers, and complexity moved into a dependency
+- `lcovBranches` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lcovFunctions` — falls when mechanisms move into Preact — externalized, not eliminated
+- `lifecycleSites` — hidden/dataset/replaceChildren go syntactically invisible under a vDOM
+
+## Owned production code (deciding)
+
+| Class | Files | Normalized (code) | Physical | Non-code |
+|---|---:|---:|---:|---:|
+| domain | 4 | **341** | 1367 | 953 |
+| plumbing | 9 | **1072** | 2091 | 920 |
+| island | 2 | **203** | 603 | 359 |
+| **total** | **15** | **1616** | **4061** | **2232** |
+
+Physical LOC overstates code by **2232** non-code lines
+(comments plus formatting-only lines). A raw-LOC comparison would have credited
+those as complexity — which is the specific error this instrument exists to prevent.
+
+## Explanatory metrics
+
+| File | Class | Code | Minified B | DOM | Listeners | Effects | Disposal | focus() | lcov BRF/FNF |
+|---|---|---:|---:|---:|---:|---:|---:|---:|---|
+| `src/application/left-nav.ts` | domain | 48 | 951 | 4 | 0 | 2 | 0 | 0 | _unmatched_ |
+| `src/core/left-nav-layout.ts` | domain | 150 | 4836 | 0 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/app-shell.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/drawer.ts` | plumbing | 56 | 993 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/left-nav-separator.ts` | island | 157 | 3170 | 12 | 14 | 2 | 4 | 0 | _unmatched_ |
+| `src/ui/left-rail.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/nav-sections.ts` | domain | 69 | 1464 | 2 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/right-inspector.ts` | plumbing | _absent_ | — | — | — | — | — | — | — |
+| `src/ui/shell/adopt.ts` | plumbing | 17 | 237 | 1 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/shell/focus-settlement.ts` | plumbing | 21 | 294 | 0 | 0 | 0 | 0 | 1 | _unmatched_ |
+| `src/ui/shell/right-inspector-view.ts` | plumbing | 192 | 2884 | 4 | 0 | 0 | 1 | 0 | _unmatched_ |
+| `src/ui/shell/shell-context.types.ts` | plumbing | 0 | 0 | 0 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/shell/shell-host.ts` | plumbing | 391 | 7169 | 21 | 2 | 17 | 23 | 3 | _unmatched_ |
+| `src/ui/shell/shell-layout.ts` | plumbing | 39 | 716 | 0 | 0 | 2 | 0 | 0 | _unmatched_ |
+| `src/ui/shell/shell-view.ts` | plumbing | 192 | 3682 | 1 | 0 | 0 | 0 | 1 | _unmatched_ |
+| `src/ui/sidebar-upper.ts` | domain | 74 | 1733 | 4 | 0 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/splitters.ts` | island | 46 | 1099 | 2 | 4 | 0 | 0 | 0 | _unmatched_ |
+| `src/ui/workbench/workbench-shell.ts` | plumbing | 164 | 5772 | 17 | 4 | 2 | 6 | 0 | _unmatched_ |
+
+> 3 manifest file(s) absent in this state — measured against the one canonical
+> manifest so every state covers an identical file set (a deletion must show up, not vanish).
+
+> 15 file(s) had no lcov record — reported as unmatched, never as zero branches.
+
+## Environment
+
+- **commit**: `3f611b5e3a2566dd56d73170239b8ff116fc834e`
+- **commitDirty**: `clean`
+- **node**: `v25.9.0`
+- **esbuild**: `0.28.1`
+- **platform**: `darwin-arm64`
+- **packageLock**: `sha256:5dd460cf3b874b6c`
diff --git a/docs/design/577/s0/bundle-size-report.json b/docs/design/577/s0/bundle-size-report.json
new file mode 100644
index 00000000..4fc2a468
--- /dev/null
+++ b/docs/design/577/s0/bundle-size-report.json
@@ -0,0 +1,386 @@
+{
+ "schemaVersion": 1,
+ "artifact": {
+ "raw": 2126521,
+ "gzip": 623736,
+ "brotli": 527859
+ },
+ "js": {
+ "raw": 1868626,
+ "gzip": 508303,
+ "brotli": 419075
+ },
+ "css": {
+ "raw": 237839,
+ "gzip": 109628,
+ "brotli": 105834
+ },
+ "totalOutputBytes": 1868430,
+ "entryPoints": [
+ {
+ "file": "main.js",
+ "entryPoint": "src/main.ts",
+ "bytes": 1868623
+ }
+ ],
+ "ownership": {
+ "project": {
+ "bytes": 629424,
+ "pct": 33.687320370578504
+ },
+ "generated": {
+ "bytes": 457367,
+ "pct": 24.47867996125089
+ },
+ "external": {
+ "bytes": 781639,
+ "pct": 41.8339996681706
+ },
+ "other": {
+ "bytes": 0,
+ "pct": 0
+ }
+ },
+ "packages": [
+ {
+ "name": "chart.js",
+ "bytes": 197174,
+ "pct": 10.55292411275777
+ },
+ {
+ "name": "@codemirror/view",
+ "bytes": 186268,
+ "pct": 9.969225499483523
+ },
+ {
+ "name": "date-fns",
+ "bytes": 47998,
+ "pct": 2.5688947405040596
+ },
+ {
+ "name": "@codemirror/state",
+ "bytes": 46747,
+ "pct": 2.5019401315542993
+ },
+ {
+ "name": "marked",
+ "bytes": 41843,
+ "pct": 2.2394737828016034
+ },
+ {
+ "name": "@dagrejs/dagre",
+ "bytes": 40422,
+ "pct": 2.163420625873059
+ },
+ {
+ "name": "@codemirror/lang-sql",
+ "bytes": 32138,
+ "pct": 1.720053734953945
+ },
+ {
+ "name": "@codemirror/autocomplete",
+ "bytes": 31522,
+ "pct": 1.6870848787484678
+ },
+ {
+ "name": "@lezer/lr",
+ "bytes": 26552,
+ "pct": 1.421086152545185
+ },
+ {
+ "name": "@codemirror/language",
+ "bytes": 24486,
+ "pct": 1.3105120341677237
+ },
+ {
+ "name": "@codemirror/commands",
+ "bytes": 23371,
+ "pct": 1.250836263600991
+ },
+ {
+ "name": "@lezer/common",
+ "bytes": 20388,
+ "pct": 1.091183507008558
+ },
+ {
+ "name": "@codemirror/search",
+ "bytes": 18472,
+ "pct": 0.9886375192006123
+ },
+ {
+ "name": "@lezer/xml",
+ "bytes": 8569,
+ "pct": 0.45862033900119353
+ },
+ {
+ "name": "@kurkle/color",
+ "bytes": 7597,
+ "pct": 0.40659805291073253
+ },
+ {
+ "name": "@lezer/highlight",
+ "bytes": 7178,
+ "pct": 0.38417280818655236
+ },
+ {
+ "name": "@codemirror/lang-xml",
+ "bytes": 5975,
+ "pct": 0.31978720101903735
+ },
+ {
+ "name": "@preact/signals-core",
+ "bytes": 4713,
+ "pct": 0.25224386249417957
+ },
+ {
+ "name": "@marijn/find-cluster-break",
+ "bytes": 2332,
+ "pct": 0.1248106699207356
+ },
+ {
+ "name": "style-mod",
+ "bytes": 2202,
+ "pct": 0.11785295676048875
+ },
+ {
+ "name": "chartjs-adapter-date-fns",
+ "bytes": 1688,
+ "pct": 0.09034322934228202
+ },
+ {
+ "name": "@lezer/json",
+ "bytes": 1599,
+ "pct": 0.08557987187103612
+ },
+ {
+ "name": "w3c-keyname",
+ "bytes": 1520,
+ "pct": 0.08135172310442457
+ },
+ {
+ "name": "crelt",
+ "bytes": 611,
+ "pct": 0.03270125185316014
+ },
+ {
+ "name": "@codemirror/lang-json",
+ "bytes": 274,
+ "pct": 0.014664718506981797
+ }
+ ],
+ "topModules": [
+ {
+ "path": "src/generated/json-schema-validators.js",
+ "bytes": 214595,
+ "pct": 11.485311197101309,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "src/generated/example-dashboards.ts",
+ "bytes": 187686,
+ "pct": 10.045118093800678,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "node_modules/@codemirror/view/dist/index.js",
+ "bytes": 186268,
+ "pct": 9.969225499483523,
+ "owner": "external",
+ "group": "@codemirror/view"
+ },
+ {
+ "path": "node_modules/chart.js/dist/chart.js",
+ "bytes": 165504,
+ "pct": 8.857918145180713,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "src/generated/json-schemas.js",
+ "bytes": 55086,
+ "pct": 2.9482506703489024,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "node_modules/@codemirror/state/dist/index.js",
+ "bytes": 46747,
+ "pct": 2.5019401315542993,
+ "owner": "external",
+ "group": "@codemirror/state"
+ },
+ {
+ "path": "node_modules/marked/lib/marked.esm.js",
+ "bytes": 41843,
+ "pct": 2.2394737828016034,
+ "owner": "external",
+ "group": "marked"
+ },
+ {
+ "path": "src/ui/app.ts",
+ "bytes": 41314,
+ "pct": 2.211161242326445,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@dagrejs/dagre/dist/dagre.esm.js",
+ "bytes": 40422,
+ "pct": 2.163420625873059,
+ "owner": "external",
+ "group": "@dagrejs/dagre"
+ },
+ {
+ "path": "src/ui/dashboard.ts",
+ "bytes": 35268,
+ "pct": 1.8875740595045039,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@codemirror/lang-sql/dist/index.js",
+ "bytes": 32138,
+ "pct": 1.720053734953945,
+ "owner": "external",
+ "group": "@codemirror/lang-sql"
+ },
+ {
+ "path": "node_modules/chart.js/dist/chunks/helpers.dataset.js",
+ "bytes": 31641,
+ "pct": 1.693453862333617,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "node_modules/@codemirror/autocomplete/dist/index.js",
+ "bytes": 31522,
+ "pct": 1.6870848787484678,
+ "owner": "external",
+ "group": "@codemirror/autocomplete"
+ },
+ {
+ "path": "node_modules/@lezer/lr/dist/index.js",
+ "bytes": 26552,
+ "pct": 1.421086152545185,
+ "owner": "external",
+ "group": "@lezer/lr"
+ },
+ {
+ "path": "node_modules/@codemirror/language/dist/index.js",
+ "bytes": 24486,
+ "pct": 1.3105120341677237,
+ "owner": "external",
+ "group": "@codemirror/language"
+ },
+ {
+ "path": "node_modules/@codemirror/commands/dist/index.js",
+ "bytes": 23371,
+ "pct": 1.250836263600991,
+ "owner": "external",
+ "group": "@codemirror/commands"
+ },
+ {
+ "path": "node_modules/@lezer/common/dist/index.js",
+ "bytes": 20388,
+ "pct": 1.091183507008558,
+ "owner": "external",
+ "group": "@lezer/common"
+ },
+ {
+ "path": "src/ui/results.ts",
+ "bytes": 18828,
+ "pct": 1.0076909490855959,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@codemirror/search/dist/index.js",
+ "bytes": 18472,
+ "pct": 0.9886375192006123,
+ "owner": "external",
+ "group": "@codemirror/search"
+ },
+ {
+ "path": "src/dashboard/application/dashboard-viewer-session.ts",
+ "bytes": 15288,
+ "pct": 0.8182270676450282,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/dashboard-tree.ts",
+ "bytes": 13899,
+ "pct": 0.7438865785713139,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/file-menu.ts",
+ "bytes": 12919,
+ "pct": 0.6914361255171454,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/core/chart-data.ts",
+ "bytes": 12028,
+ "pct": 0.6437490299342229,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/explain-graph.ts",
+ "bytes": 11980,
+ "pct": 0.6411800281519778,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/state.ts",
+ "bytes": 10933,
+ "pct": 0.585143676776759,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/shortcuts.ts",
+ "bytes": 10307,
+ "pct": 0.5516396118666475,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/net/ch-client.ts",
+ "bytes": 9860,
+ "pct": 0.5277157827694909,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/dashboard/model/workspace-semantics.ts",
+ "bytes": 8712,
+ "pct": 0.46627382347746504,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@lezer/xml/dist/index.js",
+ "bytes": 8569,
+ "pct": 0.45862033900119353,
+ "owner": "external",
+ "group": "@lezer/xml"
+ },
+ {
+ "path": "src/core/spec-schema.ts",
+ "bytes": 8272,
+ "pct": 0.44272464047355264,
+ "owner": "project",
+ "group": "src"
+ }
+ ],
+ "notes": [
+ "Percentages are of raw contributed output bytes (metafile bytesInOutput); gzip/Brotli are measured per whole artifact only — compression is not additive across modules."
+ ]
+}
\ No newline at end of file
diff --git a/docs/design/577/s0/ui-complexity-report.json b/docs/design/577/s0/ui-complexity-report.json
new file mode 100644
index 00000000..ec16be85
--- /dev/null
+++ b/docs/design/577/s0/ui-complexity-report.json
@@ -0,0 +1,352 @@
+{
+ "schemaVersion": 1,
+ "label": "s0-baseline",
+ "metricTiers": {
+ "deciding": {
+ "ownedProductionCode": {
+ "owner": "this instrument"
+ },
+ "parity": {
+ "owner": "tests/e2e/shell-parity.spec.js"
+ },
+ "artifact": {
+ "owner": "build/size-report.mjs"
+ },
+ "changeAmplification": {
+ "owner": "frozen controlled-change experiment"
+ },
+ "cleanupObligations": {
+ "owner": "leak invariant + manual obligation count"
+ }
+ },
+ "explanatory": {
+ "minifiedBytes": {
+ "demotedBecause": "ignores tree shaking, shared helpers, and complexity moved into a dependency"
+ },
+ "lcovBranches": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lcovFunctions": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lifecycleSites": {
+ "demotedBecause": "hidden/dataset/replaceChildren go syntactically invisible under a vDOM"
+ }
+ }
+ },
+ "environment": {
+ "commit": "d319847875b3b3eeff2c4ca4c43fd7dda12f8b40",
+ "commitDirty": "clean",
+ "node": "v25.9.0",
+ "esbuild": "0.28.1",
+ "platform": "darwin-arm64",
+ "packageLock": "sha256:9042ec0421903e38"
+ },
+ "ownedProductionCode": {
+ "plumbingNormalizedLines": 599,
+ "islandNormalizedLines": 199,
+ "domainNormalizedLines": 341
+ },
+ "byClass": {
+ "domain": {
+ "files": 4,
+ "absentFiles": 0,
+ "physicalLines": 1367,
+ "normalizedLines": 341,
+ "nonCodeLines": 953,
+ "minifiedBytes": 8984,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcovBranchesFound": 150,
+ "lcovFunctionsFound": 44,
+ "lcovUnmatchedFiles": 0
+ },
+ "plumbing": {
+ "files": 4,
+ "absentFiles": 1,
+ "physicalLines": 1470,
+ "normalizedLines": 599,
+ "nonCodeLines": 817,
+ "minifiedBytes": 15143,
+ "lifecycle": {
+ "domMutation": 60,
+ "listener": 7,
+ "effect": 17,
+ "disposal": 34,
+ "focus": 7
+ },
+ "lcovBranchesFound": 144,
+ "lcovFunctionsFound": 81,
+ "lcovUnmatchedFiles": 0
+ },
+ "island": {
+ "files": 2,
+ "absentFiles": 0,
+ "physicalLines": 590,
+ "normalizedLines": 199,
+ "nonCodeLines": 350,
+ "minifiedBytes": 4156,
+ "lifecycle": {
+ "domMutation": 14,
+ "listener": 18,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcovBranchesFound": 48,
+ "lcovFunctionsFound": 27,
+ "lcovUnmatchedFiles": 0
+ }
+ },
+ "overall": {
+ "files": 10,
+ "absentFiles": 1,
+ "physicalLines": 3427,
+ "normalizedLines": 1139,
+ "nonCodeLines": 2120,
+ "minifiedBytes": 28283,
+ "lifecycle": {
+ "domMutation": 84,
+ "listener": 25,
+ "effect": 21,
+ "disposal": 38,
+ "focus": 7
+ },
+ "lcovBranchesFound": 342,
+ "lcovFunctionsFound": 152,
+ "lcovUnmatchedFiles": 0
+ },
+ "files": [
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam",
+ "physicalLines": 175,
+ "sourceBlankLines": 8,
+ "normalizedLines": 48,
+ "minifiedBytes": 951,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 8,
+ "branchesHit": 8,
+ "functionsFound": 7,
+ "functionsHit": 7
+ }
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free",
+ "physicalLines": 853,
+ "sourceBlankLines": 39,
+ "normalizedLines": 150,
+ "minifiedBytes": 4836,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 124,
+ "branchesHit": 124,
+ "functionsFound": 23,
+ "functionsHit": 23
+ }
+ },
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching",
+ "physicalLines": 880,
+ "sourceBlankLines": 30,
+ "normalizedLines": 328,
+ "minifiedBytes": 7603,
+ "lifecycle": {
+ "domMutation": 39,
+ "listener": 3,
+ "effect": 14,
+ "disposal": 22,
+ "focus": 4
+ },
+ "lcov": {
+ "branchesFound": 90,
+ "branchesHit": 90,
+ "functionsFound": 39,
+ "functionsHit": 39
+ }
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes",
+ "physicalLines": 144,
+ "sourceBlankLines": 7,
+ "normalizedLines": 56,
+ "minifiedBytes": 993,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 11,
+ "branchesHit": 11,
+ "functionsFound": 10,
+ "functionsHit": 10
+ }
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics",
+ "physicalLines": 443,
+ "sourceBlankLines": 29,
+ "normalizedLines": 157,
+ "minifiedBytes": 3170,
+ "lifecycle": {
+ "domMutation": 12,
+ "listener": 14,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 28,
+ "branchesHit": 28,
+ "functionsFound": 21,
+ "functionsHit": 21
+ }
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation",
+ "physicalLines": 129,
+ "sourceBlankLines": 8,
+ "normalizedLines": 51,
+ "minifiedBytes": 775,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 1,
+ "disposal": 6,
+ "focus": 3
+ },
+ "lcov": {
+ "branchesFound": 2,
+ "branchesHit": 2,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations",
+ "physicalLines": 202,
+ "sourceBlankLines": 13,
+ "normalizedLines": 69,
+ "minifiedBytes": 1464,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 2,
+ "branchesHit": 2,
+ "functionsFound": 8,
+ "functionsHit": 8
+ }
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)",
+ "absent": true
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher",
+ "physicalLines": 137,
+ "sourceBlankLines": 13,
+ "normalizedLines": 74,
+ "minifiedBytes": 1733,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 16,
+ "branchesHit": 15,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector",
+ "physicalLines": 147,
+ "sourceBlankLines": 12,
+ "normalizedLines": 42,
+ "minifiedBytes": 986,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 4,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 20,
+ "branchesHit": 20,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host",
+ "physicalLines": 317,
+ "sourceBlankLines": 9,
+ "normalizedLines": 164,
+ "minifiedBytes": 5772,
+ "lifecycle": {
+ "domMutation": 17,
+ "listener": 4,
+ "effect": 2,
+ "disposal": 6,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 41,
+ "branchesHit": 40,
+ "functionsFound": 26,
+ "functionsHit": 26
+ }
+ }
+ ]
+}
diff --git a/docs/design/577/s1/bundle-size-report.json b/docs/design/577/s1/bundle-size-report.json
new file mode 100644
index 00000000..8476b8bb
--- /dev/null
+++ b/docs/design/577/s1/bundle-size-report.json
@@ -0,0 +1,386 @@
+{
+ "schemaVersion": 1,
+ "artifact": {
+ "raw": 2130619,
+ "gzip": 624764,
+ "brotli": 528747
+ },
+ "js": {
+ "raw": 1870835,
+ "gzip": 509126,
+ "brotli": 419745
+ },
+ "css": {
+ "raw": 239728,
+ "gzip": 109836,
+ "brotli": 105960
+ },
+ "totalOutputBytes": 1870639,
+ "entryPoints": [
+ {
+ "file": "main.js",
+ "entryPoint": "src/main.ts",
+ "bytes": 1870832
+ }
+ ],
+ "ownership": {
+ "project": {
+ "bytes": 631633,
+ "pct": 33.7656276812362
+ },
+ "generated": {
+ "bytes": 457367,
+ "pct": 24.449773580044038
+ },
+ "external": {
+ "bytes": 781639,
+ "pct": 41.784598738719765
+ },
+ "other": {
+ "bytes": 0,
+ "pct": 0
+ }
+ },
+ "packages": [
+ {
+ "name": "chart.js",
+ "bytes": 197174,
+ "pct": 10.540462376760026
+ },
+ {
+ "name": "@codemirror/view",
+ "bytes": 186268,
+ "pct": 9.957453041447334
+ },
+ {
+ "name": "date-fns",
+ "bytes": 47998,
+ "pct": 2.5658611843332677
+ },
+ {
+ "name": "@codemirror/state",
+ "bytes": 46747,
+ "pct": 2.498985640735599
+ },
+ {
+ "name": "marked",
+ "bytes": 41843,
+ "pct": 2.236829233219237
+ },
+ {
+ "name": "@dagrejs/dagre",
+ "bytes": 40422,
+ "pct": 2.160865885935234
+ },
+ {
+ "name": "@codemirror/lang-sql",
+ "bytes": 32138,
+ "pct": 1.718022558067056
+ },
+ {
+ "name": "@codemirror/autocomplete",
+ "bytes": 31522,
+ "pct": 1.6850926341212817
+ },
+ {
+ "name": "@lezer/lr",
+ "bytes": 26552,
+ "pct": 1.419408020467872
+ },
+ {
+ "name": "@codemirror/language",
+ "bytes": 24486,
+ "pct": 1.3089644768445434
+ },
+ {
+ "name": "@codemirror/commands",
+ "bytes": 23371,
+ "pct": 1.2493591761959415
+ },
+ {
+ "name": "@lezer/common",
+ "bytes": 20388,
+ "pct": 1.089894950335153
+ },
+ {
+ "name": "@codemirror/search",
+ "bytes": 18472,
+ "pct": 0.9874700570232953
+ },
+ {
+ "name": "@lezer/xml",
+ "bytes": 8569,
+ "pct": 0.4580787634599728
+ },
+ {
+ "name": "@kurkle/color",
+ "bytes": 7597,
+ "pct": 0.40611790944164
+ },
+ {
+ "name": "@lezer/highlight",
+ "bytes": 7178,
+ "pct": 0.38371914623826403
+ },
+ {
+ "name": "@codemirror/lang-xml",
+ "bytes": 5975,
+ "pct": 0.3194095707402657
+ },
+ {
+ "name": "@preact/signals-core",
+ "bytes": 4713,
+ "pct": 0.25194599278642216
+ },
+ {
+ "name": "@marijn/find-cluster-break",
+ "bytes": 2332,
+ "pct": 0.12466328350900416
+ },
+ {
+ "name": "style-mod",
+ "bytes": 2202,
+ "pct": 0.11771378657239584
+ },
+ {
+ "name": "chartjs-adapter-date-fns",
+ "bytes": 1688,
+ "pct": 0.09023654483842151
+ },
+ {
+ "name": "@lezer/json",
+ "bytes": 1599,
+ "pct": 0.085478812320282
+ },
+ {
+ "name": "w3c-keyname",
+ "bytes": 1520,
+ "pct": 0.08125565648957389
+ },
+ {
+ "name": "crelt",
+ "bytes": 611,
+ "pct": 0.032662635602058974
+ },
+ {
+ "name": "@codemirror/lang-json",
+ "bytes": 274,
+ "pct": 0.014647401235620555
+ }
+ ],
+ "topModules": [
+ {
+ "path": "src/generated/json-schema-validators.js",
+ "bytes": 214595,
+ "pct": 11.471748423934281,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "src/generated/example-dashboards.ts",
+ "bytes": 187686,
+ "pct": 10.033256015725108,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "node_modules/@codemirror/view/dist/index.js",
+ "bytes": 186268,
+ "pct": 9.957453041447334,
+ "owner": "external",
+ "group": "@codemirror/view"
+ },
+ {
+ "path": "node_modules/chart.js/dist/chart.js",
+ "bytes": 165504,
+ "pct": 8.84745800766476,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "src/generated/json-schemas.js",
+ "bytes": 55086,
+ "pct": 2.944769140384649,
+ "owner": "generated",
+ "group": "src/generated"
+ },
+ {
+ "path": "node_modules/@codemirror/state/dist/index.js",
+ "bytes": 46747,
+ "pct": 2.498985640735599,
+ "owner": "external",
+ "group": "@codemirror/state"
+ },
+ {
+ "path": "node_modules/marked/lib/marked.esm.js",
+ "bytes": 41843,
+ "pct": 2.236829233219237,
+ "owner": "external",
+ "group": "marked"
+ },
+ {
+ "path": "src/ui/app.ts",
+ "bytes": 41314,
+ "pct": 2.2085501264541154,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@dagrejs/dagre/dist/dagre.esm.js",
+ "bytes": 40422,
+ "pct": 2.160865885935234,
+ "owner": "external",
+ "group": "@dagrejs/dagre"
+ },
+ {
+ "path": "src/ui/dashboard.ts",
+ "bytes": 35268,
+ "pct": 1.8853450612330866,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@codemirror/lang-sql/dist/index.js",
+ "bytes": 32138,
+ "pct": 1.718022558067056,
+ "owner": "external",
+ "group": "@codemirror/lang-sql"
+ },
+ {
+ "path": "node_modules/chart.js/dist/chunks/helpers.dataset.js",
+ "bytes": 31641,
+ "pct": 1.6914540967017153,
+ "owner": "external",
+ "group": "chart.js"
+ },
+ {
+ "path": "node_modules/@codemirror/autocomplete/dist/index.js",
+ "bytes": 31522,
+ "pct": 1.6850926341212817,
+ "owner": "external",
+ "group": "@codemirror/autocomplete"
+ },
+ {
+ "path": "node_modules/@lezer/lr/dist/index.js",
+ "bytes": 26552,
+ "pct": 1.419408020467872,
+ "owner": "external",
+ "group": "@lezer/lr"
+ },
+ {
+ "path": "node_modules/@codemirror/language/dist/index.js",
+ "bytes": 24486,
+ "pct": 1.3089644768445434,
+ "owner": "external",
+ "group": "@codemirror/language"
+ },
+ {
+ "path": "node_modules/@codemirror/commands/dist/index.js",
+ "bytes": 23371,
+ "pct": 1.2493591761959415,
+ "owner": "external",
+ "group": "@codemirror/commands"
+ },
+ {
+ "path": "node_modules/@lezer/common/dist/index.js",
+ "bytes": 20388,
+ "pct": 1.089894950335153,
+ "owner": "external",
+ "group": "@lezer/common"
+ },
+ {
+ "path": "src/ui/results.ts",
+ "bytes": 18828,
+ "pct": 1.0065009870958534,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@codemirror/search/dist/index.js",
+ "bytes": 18472,
+ "pct": 0.9874700570232953,
+ "owner": "external",
+ "group": "@codemirror/search"
+ },
+ {
+ "path": "src/dashboard/application/dashboard-viewer-session.ts",
+ "bytes": 15288,
+ "pct": 0.8172608397451353,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/dashboard-tree.ts",
+ "bytes": 13899,
+ "pct": 0.7430081378609127,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/file-menu.ts",
+ "bytes": 12919,
+ "pct": 0.6906196224926349,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/core/chart-data.ts",
+ "bytes": 12028,
+ "pct": 0.6429888396424965,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/explain-graph.ts",
+ "bytes": 11980,
+ "pct": 0.6404228715428257,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/state.ts",
+ "bytes": 11032,
+ "pct": 0.5897450015743284,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/ui/shortcuts.ts",
+ "bytes": 10307,
+ "pct": 0.5509881917355514,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/net/ch-client.ts",
+ "bytes": 9860,
+ "pct": 0.5270926138073675,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "src/dashboard/model/workspace-semantics.ts",
+ "bytes": 8712,
+ "pct": 0.4657232100902419,
+ "owner": "project",
+ "group": "src"
+ },
+ {
+ "path": "node_modules/@lezer/xml/dist/index.js",
+ "bytes": 8569,
+ "pct": 0.4580787634599728,
+ "owner": "external",
+ "group": "@lezer/xml"
+ },
+ {
+ "path": "src/core/spec-schema.ts",
+ "bytes": 8272,
+ "pct": 0.44220183584325995,
+ "owner": "project",
+ "group": "src"
+ }
+ ],
+ "notes": [
+ "Percentages are of raw contributed output bytes (metafile bytesInOutput); gzip/Brotli are measured per whole artifact only — compression is not additive across modules."
+ ]
+}
\ No newline at end of file
diff --git a/docs/design/577/s1/ui-complexity-report.json b/docs/design/577/s1/ui-complexity-report.json
new file mode 100644
index 00000000..d28797cb
--- /dev/null
+++ b/docs/design/577/s1/ui-complexity-report.json
@@ -0,0 +1,363 @@
+{
+ "schemaVersion": 1,
+ "label": "s1-control",
+ "metricTiers": {
+ "deciding": {
+ "ownedProductionCode": {
+ "owner": "this instrument"
+ },
+ "parity": {
+ "owner": "tests/e2e/shell-parity.spec.js"
+ },
+ "artifact": {
+ "owner": "build/size-report.mjs"
+ },
+ "changeAmplification": {
+ "owner": "frozen controlled-change experiment"
+ },
+ "cleanupObligations": {
+ "owner": "leak invariant + manual obligation count"
+ }
+ },
+ "explanatory": {
+ "minifiedBytes": {
+ "demotedBecause": "ignores tree shaking, shared helpers, and complexity moved into a dependency"
+ },
+ "lcovBranches": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lcovFunctions": {
+ "demotedBecause": "falls when mechanisms move into Preact — externalized, not eliminated"
+ },
+ "lifecycleSites": {
+ "demotedBecause": "hidden/dataset/replaceChildren go syntactically invisible under a vDOM"
+ }
+ }
+ },
+ "environment": {
+ "commit": "7e7c8c4b6d8c5853d4e59dc599c5705f8e9cacab",
+ "commitDirty": "clean",
+ "node": "v25.9.0",
+ "esbuild": "0.28.1",
+ "platform": "darwin-arm64",
+ "packageLock": "sha256:9042ec0421903e38"
+ },
+ "ownedProductionCode": {
+ "plumbingNormalizedLines": 742,
+ "islandNormalizedLines": 203,
+ "domainNormalizedLines": 341
+ },
+ "byClass": {
+ "domain": {
+ "files": 4,
+ "absentFiles": 0,
+ "physicalLines": 1367,
+ "normalizedLines": 341,
+ "nonCodeLines": 953,
+ "minifiedBytes": 8984,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcovBranchesFound": 150,
+ "lcovFunctionsFound": 44,
+ "lcovUnmatchedFiles": 0
+ },
+ "plumbing": {
+ "files": 5,
+ "absentFiles": 0,
+ "physicalLines": 1766,
+ "normalizedLines": 742,
+ "nonCodeLines": 949,
+ "minifiedBytes": 17444,
+ "lifecycle": {
+ "domMutation": 72,
+ "listener": 7,
+ "effect": 17,
+ "disposal": 36,
+ "focus": 8
+ },
+ "lcovBranchesFound": 144,
+ "lcovFunctionsFound": 81,
+ "lcovUnmatchedFiles": 1
+ },
+ "island": {
+ "files": 2,
+ "absentFiles": 0,
+ "physicalLines": 603,
+ "normalizedLines": 203,
+ "nonCodeLines": 359,
+ "minifiedBytes": 4269,
+ "lifecycle": {
+ "domMutation": 14,
+ "listener": 18,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcovBranchesFound": 48,
+ "lcovFunctionsFound": 27,
+ "lcovUnmatchedFiles": 0
+ }
+ },
+ "overall": {
+ "files": 11,
+ "absentFiles": 0,
+ "physicalLines": 3736,
+ "normalizedLines": 1286,
+ "nonCodeLines": 2261,
+ "minifiedBytes": 30697,
+ "lifecycle": {
+ "domMutation": 96,
+ "listener": 25,
+ "effect": 21,
+ "disposal": 40,
+ "focus": 8
+ },
+ "lcovBranchesFound": 342,
+ "lcovFunctionsFound": 152,
+ "lcovUnmatchedFiles": 1
+ },
+ "files": [
+ {
+ "path": "src/application/left-nav.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — the focused-section controller seam",
+ "physicalLines": 175,
+ "sourceBlankLines": 8,
+ "normalizedLines": 48,
+ "minifiedBytes": 951,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 2,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 8,
+ "branchesHit": 8,
+ "functionsFound": 7,
+ "functionsHit": 7
+ }
+ },
+ {
+ "path": "src/core/left-nav-layout.ts",
+ "class": "domain",
+ "sliceItem": "2, 3 — pure mode/resize reducers, DOM-free",
+ "physicalLines": 853,
+ "sourceBlankLines": 39,
+ "normalizedLines": 150,
+ "minifiedBytes": 4836,
+ "lifecycle": {
+ "domMutation": 0,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 124,
+ "branchesHit": 124,
+ "functionsFound": 23,
+ "functionsHit": 23
+ }
+ },
+ {
+ "path": "src/ui/app-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "1, 5, 6 — shell geometry below the header; desktop/mobile projection; Query/Dashboard host switching",
+ "physicalLines": 905,
+ "sourceBlankLines": 31,
+ "normalizedLines": 340,
+ "minifiedBytes": 7856,
+ "lifecycle": {
+ "domMutation": 41,
+ "listener": 3,
+ "effect": 14,
+ "disposal": 23,
+ "focus": 4
+ },
+ "lcov": {
+ "branchesFound": 90,
+ "branchesHit": 90,
+ "functionsFound": 39,
+ "functionsHit": 39
+ }
+ },
+ {
+ "path": "src/ui/drawer.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — right-inspector chrome and bounded resize primitives the control composes",
+ "physicalLines": 144,
+ "sourceBlankLines": 7,
+ "normalizedLines": 56,
+ "minifiedBytes": 993,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 11,
+ "branchesHit": 11,
+ "functionsFound": 10,
+ "functionsHit": 10
+ }
+ },
+ {
+ "path": "src/ui/left-nav-separator.ts",
+ "class": "island",
+ "sliceItem": "3 — the multi-frame fold transition's pointer/keyboard mechanics",
+ "physicalLines": 443,
+ "sourceBlankLines": 29,
+ "normalizedLines": 157,
+ "minifiedBytes": 3170,
+ "lifecycle": {
+ "domMutation": 12,
+ "listener": 14,
+ "effect": 2,
+ "disposal": 4,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 28,
+ "branchesHit": 28,
+ "functionsFound": 21,
+ "functionsHit": 21
+ }
+ },
+ {
+ "path": "src/ui/left-rail.ts",
+ "class": "plumbing",
+ "sliceItem": "2 — left navigation in rail presentation",
+ "physicalLines": 129,
+ "sourceBlankLines": 8,
+ "normalizedLines": 51,
+ "minifiedBytes": 775,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 1,
+ "disposal": 6,
+ "focus": 3
+ },
+ "lcov": {
+ "branchesFound": 2,
+ "branchesHit": 2,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/nav-sections.ts",
+ "class": "domain",
+ "sliceItem": "2 — section registry shared by wide, rail and drawer presentations",
+ "physicalLines": 202,
+ "sourceBlankLines": 13,
+ "normalizedLines": 69,
+ "minifiedBytes": 1464,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 2,
+ "branchesHit": 2,
+ "functionsFound": 8,
+ "functionsHit": 8
+ }
+ },
+ {
+ "path": "src/ui/right-inspector.ts",
+ "class": "plumbing",
+ "sliceItem": "4 — the right-inspector host itself (absent in S0; added by the S1 control)",
+ "physicalLines": 271,
+ "sourceBlankLines": 20,
+ "normalizedLines": 131,
+ "minifiedBytes": 2048,
+ "lifecycle": {
+ "domMutation": 10,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 1,
+ "focus": 1
+ },
+ "lcov": null
+ },
+ {
+ "path": "src/ui/sidebar-upper.ts",
+ "class": "domain",
+ "sliceItem": "2 — upper-pane role switcher",
+ "physicalLines": 137,
+ "sourceBlankLines": 13,
+ "normalizedLines": 74,
+ "minifiedBytes": 1733,
+ "lifecycle": {
+ "domMutation": 4,
+ "listener": 0,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 16,
+ "branchesHit": 15,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/splitters.ts",
+ "class": "island",
+ "sliceItem": "3, 4 — drag mechanics for both the left separator and the right inspector",
+ "physicalLines": 160,
+ "sourceBlankLines": 12,
+ "normalizedLines": 46,
+ "minifiedBytes": 1099,
+ "lifecycle": {
+ "domMutation": 2,
+ "listener": 4,
+ "effect": 0,
+ "disposal": 0,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 20,
+ "branchesHit": 20,
+ "functionsFound": 6,
+ "functionsHit": 6
+ }
+ },
+ {
+ "path": "src/ui/workbench/workbench-shell.ts",
+ "class": "plumbing",
+ "sliceItem": "6 — Query surface host",
+ "physicalLines": 317,
+ "sourceBlankLines": 9,
+ "normalizedLines": 164,
+ "minifiedBytes": 5772,
+ "lifecycle": {
+ "domMutation": 17,
+ "listener": 4,
+ "effect": 2,
+ "disposal": 6,
+ "focus": 0
+ },
+ "lcov": {
+ "branchesFound": 41,
+ "branchesHit": 40,
+ "functionsFound": 26,
+ "functionsHit": 26
+ }
+ }
+ ]
+}
diff --git a/tests/unit/ui-complexity-report.test.js b/tests/unit/ui-complexity-report.test.js
new file mode 100644
index 00000000..f1cf2874
--- /dev/null
+++ b/tests/unit/ui-complexity-report.test.js
@@ -0,0 +1,459 @@
+// Tests for the #577 UI-complexity measurement instrument.
+//
+// Two jobs. The ordinary one is covering the pure counting rules in
+// `build/ui-complexity-lib.mjs`. The important one is the SABOTAGE suite: the
+// instrument's whole claim to being decision-grade is that it counts code and
+// not this repository's very heavy comment convention, so there are tests here
+// that FAIL if comment-stripping regresses. That mirrors the convention in
+// `tests/unit/typography-contract.test.js` — a gate is only evidence if it can
+// be shown to fail.
+//
+// `build/` sits outside the coverage `include` glob (`src/**/*.{js,ts}`), so
+// these tests are not driven by the per-file floor; they exist because a
+// measurement instrument nobody tested is not reproducible, which is #577's
+// acceptance criterion 5.
+
+import { describe, expect, it } from 'vitest';
+import { readFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+import * as esbuild from 'esbuild';
+import {
+ COMPLEXITY_SCHEMA_VERSION,
+ FILE_CLASSES,
+ METRIC_TIERS,
+ buildComplexityReport,
+ computeComplexityDelta,
+ countLifecycleSites,
+ countNormalizedLines,
+ diffComplexityReports,
+ lookupLcov,
+ parseLcov,
+ renderComplexityMarkdown,
+} from '../../build/ui-complexity-lib.mjs';
+
+const repoRoot = resolve(import.meta.dirname, '../..');
+
+/** Build a manifest-shaped entry with sane defaults so each test only states
+ * the fields it cares about. */
+function entry(overrides = {}) {
+ return {
+ path: 'src/ui/x.ts',
+ class: 'plumbing',
+ sliceItem: '1',
+ physicalLines: 10,
+ sourceBlankLines: 2,
+ normalizedLines: 5,
+ minifiedBytes: 100,
+ lifecycle: { domMutation: 0, listener: 0, effect: 0, disposal: 0, focus: 0 },
+ lcov: { branchesFound: 4, branchesHit: 4, functionsFound: 2, functionsHit: 2 },
+ ...overrides,
+ };
+}
+
+describe('countNormalizedLines', () => {
+ it('counts non-blank lines and ignores a trailing newline', () => {
+ expect(countNormalizedLines('a\nb\n')).toEqual({ normalized: 2, blank: 1 });
+ });
+
+ it('does not count whitespace-only lines as code', () => {
+ expect(countNormalizedLines('a\n \n\t\nb')).toEqual({ normalized: 2, blank: 2 });
+ });
+
+ it('reports zero for empty input rather than throwing', () => {
+ expect(countNormalizedLines('')).toEqual({ normalized: 0, blank: 1 });
+ });
+});
+
+describe('countLifecycleSites', () => {
+ it('counts OCCURRENCES, not matching lines', () => {
+ // The first implementation of this measurement used `rg -c`, which counts
+ // LINES — so this single line scored 1 instead of 3 and every per-file
+ // number in the report was an undercount.
+ const code = 'el.hidden = true; other.hidden = false; third.hidden = true;';
+ expect(countLifecycleSites(code).domMutation).toBe(3);
+ });
+
+ it('counts each DOM-mutation family the issue names', () => {
+ const code = [
+ 'a.hidden = true;',
+ 'b.dataset.navMode = "wide";',
+ 'c.textContent = "x";',
+ 'd.replaceChildren(e);',
+ 'f.setAttribute("aria-labelledby", g);',
+ 'h.removeAttribute("aria-labelledby");',
+ 'i.style.width = "1px";',
+ 'j.classList.add("k");',
+ ].join('\n');
+ expect(countLifecycleSites(code).domMutation).toBe(8);
+ });
+
+ it('separates listeners, effects, disposal and focus', () => {
+ const code = [
+ 'el.addEventListener("keydown", f);',
+ 'el.removeEventListener("keydown", f);',
+ 'disposers.push(effect(() => { untracked(() => s.value); }));',
+ 'batch(() => {});',
+ 'const c = computed(() => 1);',
+ 'thing.dispose();',
+ 'button.focus();',
+ ].join('\n');
+ const sites = countLifecycleSites(code);
+ expect(sites.listener).toBe(2);
+ expect(sites.effect).toBe(4);
+ expect(sites.focus).toBe(1);
+ expect(sites.disposal).toBeGreaterThanOrEqual(2);
+ });
+});
+
+// ---------------------------------------------------------------------------
+// SABOTAGE SUITE — these are the tests that make the instrument defensible.
+// ---------------------------------------------------------------------------
+describe('comment stripping (sabotage cases)', () => {
+ /** Exactly what the runner does: esbuild-transform, then count. */
+ async function measure(source, loader = 'ts') {
+ const stripped = await esbuild.transform(source, { loader });
+ return {
+ code: stripped.code,
+ lines: countNormalizedLines(stripped.code).normalized,
+ sites: countLifecycleSites(stripped.code),
+ };
+ }
+
+ it('does not count comment-only lines as code', async () => {
+ const source = [
+ '// one',
+ '// two',
+ '/* three',
+ ' four */',
+ 'const a = 1;',
+ ].join('\n');
+ expect((await measure(source)).lines).toBe(1);
+ });
+
+ it('does not count DOM mutations that appear only inside a comment', async () => {
+ // This is the concrete bug the first pass at this measurement had: run over
+ // RAW source, a regex matched the prose in this repo's rationale comments,
+ // reporting 37 "mutation sites" in app-shell.ts where the stripped code has
+ // a different, smaller number.
+ const source = [
+ '// Every `hidden`/text toggle below is set UNCONDITIONALLY, and',
+ '// el.dataset.navMode is written here too, plus a.replaceChildren(b).',
+ 'const noop = 1;',
+ ].join('\n');
+ const { sites } = await measure(source);
+ expect(sites.domMutation).toBe(0);
+ });
+
+ it('keeps a "//" that lives inside a string literal', async () => {
+ const source = 'const url = "http://example.com//path";';
+ const { code, lines } = await measure(source);
+ expect(lines).toBe(1);
+ expect(code).toContain('//path');
+ });
+
+ it('keeps an escaped "//" inside a regex literal', async () => {
+ // A hand-rolled scanner tracking only quotes treats the `\/\/` here as the
+ // start of a line comment and silently drops the rest of the line.
+ const source = 'const re = /https:\\/\\/x/g;\nel.hidden = true;';
+ const { lines, sites } = await measure(source);
+ expect(lines).toBe(2);
+ expect(sites.domMutation).toBe(1);
+ });
+
+ it('keeps a "*"-prefixed line inside a template literal', async () => {
+ // A scanner that skips lines whose trimmed form starts with `*` (to handle
+ // jsdoc continuation) eats real code here.
+ const source = 'const t = `\n * not a comment\n`;\nel.focus();';
+ const { code, sites } = await measure(source);
+ expect(code).toContain('not a comment');
+ expect(sites.focus).toBe(1);
+ });
+
+ it('strips a trailing comment without dropping the code before it', async () => {
+ const source = 'el.hidden = true; // rationale here mentions .dataset.x';
+ const { lines, sites } = await measure(source);
+ expect(lines).toBe(1);
+ expect(sites.domMutation).toBe(1);
+ });
+
+ it('normalizes formatting, so collapsing lines cannot win', async () => {
+ // Without normalization an arm could halve its "LOC" purely by reformatting.
+ const spread = 'function f(\n a,\n b\n) {\n return a + b;\n}';
+ const dense = 'function f(a, b) { return a + b; }';
+ expect((await measure(spread)).lines).toBe((await measure(dense)).lines);
+ });
+
+ it('counting raw source really would inflate the numbers (the bug is reachable)', async () => {
+ // Guards against this suite becoming vacuous. Every test above asserts that
+ // STRIPPED code counts correctly — but esbuild's stripping cannot regress,
+ // so on its own that proves nothing about our choice to strip. This one
+ // pins the actual delta: the same input scores higher un-stripped, so the
+ // strip step is load-bearing rather than decorative.
+ const source = [
+ '// sets el.hidden = true and writes el.dataset.navMode',
+ 'const noop = 1;',
+ ].join('\n');
+ const raw = countLifecycleSites(source).domMutation;
+ const stripped = countLifecycleSites((await esbuild.transform(source, { loader: 'ts' })).code).domMutation;
+ expect(raw).toBeGreaterThan(0);
+ expect(stripped).toBe(0);
+ });
+
+ it('the runner counts stripped code, not raw source', async () => {
+ // A structural guard on the one decision that makes every number in the
+ // report trustworthy. The lib is pure by design (no fs, no esbuild), so the
+ // strip-then-count wiring lives in the runner and is otherwise unguarded:
+ // swapping `stripped.code` for `source` there would leave all 30+ tests
+ // above green while silently restoring the comment-inflated counts.
+ const runner = await readFile(resolve(repoRoot, 'build/ui-complexity-report.mjs'), 'utf8');
+ expect(runner).toMatch(/countLifecycleSites\(\s*stripped\.code\s*\)/);
+ expect(runner).toMatch(/countNormalizedLines\(\s*stripped\.code\s*\)/);
+ expect(runner).not.toMatch(/countLifecycleSites\(\s*source\s*\)/);
+ });
+});
+
+describe('parseLcov / lookupLcov', () => {
+ const lcov = [
+ 'SF:/abs/repo/src/ui/app-shell.ts',
+ 'FNF:39',
+ 'FNH:38',
+ 'BRF:90',
+ 'BRH:85',
+ 'end_of_record',
+ 'SF:/abs/repo/src/ui/left-rail.ts',
+ 'FNF:6',
+ 'FNH:6',
+ 'BRF:2',
+ 'BRH:2',
+ 'end_of_record',
+ ].join('\n');
+
+ it('parses the found/hit totals per file', () => {
+ const parsed = parseLcov(lcov);
+ expect(parsed['/abs/repo/src/ui/app-shell.ts']).toEqual({
+ branchesFound: 90, branchesHit: 85, functionsFound: 39, functionsHit: 38,
+ });
+ });
+
+ it('resolves a repo-relative manifest path against an absolute lcov key', () => {
+ // lcov writes absolute paths here while the manifest is repo-relative; a
+ // plain lookup misses every file and would report zero branches everywhere.
+ const parsed = parseLcov(lcov);
+ expect(lookupLcov(parsed, 'src/ui/left-rail.ts').functionsFound).toBe(6);
+ });
+
+ it('returns null — never a zero record — for a file with no lcov data', () => {
+ // "No coverage data" and "no branches" are different claims, and conflating
+ // them would let a missing measurement read as a perfect one.
+ expect(lookupLcov(parseLcov(lcov), 'src/ui/absent.ts')).toBeNull();
+ });
+
+ it('does not match a path that merely shares a filename suffix', () => {
+ const parsed = parseLcov('SF:/abs/repo/src/ui/rail.ts\nBRF:1\nend_of_record');
+ expect(lookupLcov(parsed, 'src/ui/left-rail.ts')).toBeNull();
+ });
+});
+
+describe('buildComplexityReport', () => {
+ it('rejects an unknown file class instead of silently bucketing it', () => {
+ // The manifest is acceptance criterion 4's auditable artifact, so a typo in
+ // a class name has to be loud.
+ expect(() => buildComplexityReport({ entries: [entry({ class: 'plumbling' })] }))
+ .toThrow(/unknown class "plumbling"/);
+ });
+
+ it('totals separately per class so domain is never credited to plumbing', () => {
+ const report = buildComplexityReport({
+ entries: [
+ entry({ path: 'a.ts', class: 'plumbing', normalizedLines: 10 }),
+ entry({ path: 'b.ts', class: 'domain', normalizedLines: 20 }),
+ entry({ path: 'c.ts', class: 'island', normalizedLines: 30 }),
+ ],
+ });
+ expect(report.byClass.plumbing.normalizedLines).toBe(10);
+ expect(report.byClass.domain.normalizedLines).toBe(20);
+ expect(report.byClass.island.normalizedLines).toBe(30);
+ expect(report.overall.normalizedLines).toBe(60);
+ });
+
+ it('exposes the deciding metric under its own name', () => {
+ const report = buildComplexityReport({
+ entries: [entry({ class: 'plumbing', normalizedLines: 7 })],
+ });
+ expect(report.ownedProductionCode.plumbingNormalizedLines).toBe(7);
+ });
+
+ it('derives non-code lines as physical minus blank minus code', () => {
+ const report = buildComplexityReport({
+ entries: [entry({ physicalLines: 100, sourceBlankLines: 10, normalizedLines: 30 })],
+ });
+ expect(report.overall.nonCodeLines).toBe(60);
+ });
+
+ it('records an absent file without counting it as zero-sized code', () => {
+ // The comparability rule: one canonical manifest across every state, so a
+ // file that disappears is reported as absent rather than silently dropped —
+ // for the treatment state, that absence IS the headline result.
+ const report = buildComplexityReport({
+ entries: [
+ entry({ path: 'kept.ts', normalizedLines: 40 }),
+ { path: 'deleted.ts', class: 'plumbing', absent: true },
+ ],
+ });
+ expect(report.overall.absentFiles).toBe(1);
+ expect(report.overall.files).toBe(1);
+ expect(report.overall.normalizedLines).toBe(40);
+ });
+
+ it('marks an absent file in the rendered report instead of omitting the row', () => {
+ const md = renderComplexityMarkdown(buildComplexityReport({
+ entries: [{ path: 'deleted.ts', class: 'plumbing', absent: true }],
+ }));
+ expect(md).toContain('deleted.ts');
+ expect(md).toContain('_absent_');
+ expect(md).toMatch(/a deletion must show up, not vanish/);
+ });
+
+ it('still validates the class of an absent file', () => {
+ expect(() => buildComplexityReport({ entries: [{ path: 'x.ts', class: 'bogus', absent: true }] }))
+ .toThrow(/unknown class/);
+ });
+
+ it('counts files with no lcov record as unmatched', () => {
+ const report = buildComplexityReport({ entries: [entry({ lcov: null })] });
+ expect(report.overall.lcovUnmatchedFiles).toBe(1);
+ expect(report.overall.lcovBranchesFound).toBe(0);
+ });
+
+ it('emits the metric tiering, so a demoted metric cannot be quietly promoted', () => {
+ const report = buildComplexityReport({ entries: [entry()] });
+ expect(report.metricTiers).toBe(METRIC_TIERS);
+ expect(report.metricTiers.explanatory.lcovBranches.demotedBecause).toMatch(/externalized/);
+ expect(Object.keys(report.metricTiers.deciding)).toContain('changeAmplification');
+ });
+
+ it('sorts files deterministically so two reports diff cleanly', () => {
+ const report = buildComplexityReport({
+ entries: [entry({ path: 'z.ts' }), entry({ path: 'a.ts' })],
+ });
+ expect(report.files.map((f) => f.path)).toEqual(['a.ts', 'z.ts']);
+ });
+
+ it('stamps the schema version', () => {
+ expect(buildComplexityReport({ entries: [entry()] }).schemaVersion).toBe(COMPLEXITY_SCHEMA_VERSION);
+ });
+});
+
+describe('deltas', () => {
+ it('computes absolute and percentage change', () => {
+ expect(computeComplexityDelta(120, 100)).toEqual({ current: 120, base: 100, abs: 20, pct: 20 });
+ });
+
+ it('reports a null percentage against a zero base rather than Infinity', () => {
+ // Printing `Infinity%` in a decision document is worse than printing nothing.
+ expect(computeComplexityDelta(5, 0).pct).toBeNull();
+ });
+
+ it('diffs per class and overall', () => {
+ const mk = (n) => buildComplexityReport({ entries: [entry({ normalizedLines: n })] });
+ const deltas = diffComplexityReports(mk(80), mk(100));
+ expect(deltas.byClass.plumbing.normalizedLines.abs).toBe(-20);
+ expect(deltas.overall.normalizedLines.abs).toBe(-20);
+ });
+});
+
+describe('renderComplexityMarkdown', () => {
+ it('states the demotion reason for every explanatory metric', () => {
+ const md = renderComplexityMarkdown(buildComplexityReport({ entries: [entry()] }));
+ for (const metric of Object.keys(METRIC_TIERS.explanatory)) {
+ expect(md).toContain(`\`${metric}\``);
+ }
+ expect(md).toMatch(/must not be voted with/);
+ });
+
+ it('flags unmatched lcov files instead of printing a zero', () => {
+ const md = renderComplexityMarkdown(buildComplexityReport({ entries: [entry({ lcov: null })] }));
+ expect(md).toContain('_unmatched_');
+ expect(md).toMatch(/never as zero branches/);
+ });
+
+ it('renders a delta section only when a base is supplied', () => {
+ const report = buildComplexityReport({ entries: [entry()] });
+ expect(renderComplexityMarkdown(report)).not.toContain('Delta vs base');
+ expect(renderComplexityMarkdown(report, diffComplexityReports(report, report)))
+ .toContain('Delta vs base');
+ });
+
+ it('renders the captured environment when present', () => {
+ const report = buildComplexityReport({ entries: [entry()], environment: { commit: 'abc123' } });
+ expect(renderComplexityMarkdown(report)).toContain('abc123');
+ });
+});
+
+describe('the committed manifest', () => {
+ /** The measured file set, pinned.
+ *
+ * Deliberately a PINNED LIST rather than a filesystem-existence check. The
+ * same manifest is measured against every evaluation state, and a file is
+ * legitimately absent in some of them — the inspector does not exist in S0,
+ * and the vanilla shell does not exist in S2, which is the single most
+ * decision-relevant fact the whole report carries. An existence assertion
+ * would therefore fail on the states it matters most on.
+ *
+ * A pin still catches what actually needs catching: a rename or an accidental
+ * drop silently shrinks the measured baseline, and that is exactly the kind of
+ * undetectable bias this instrument exists to prevent. Changing the set now
+ * requires consciously changing this list. */
+ const PINNED_PATHS = [
+ 'src/application/left-nav.ts',
+ 'src/core/left-nav-layout.ts',
+ 'src/ui/app-shell.ts',
+ 'src/ui/drawer.ts',
+ 'src/ui/left-nav-separator.ts',
+ 'src/ui/left-rail.ts',
+ 'src/ui/nav-sections.ts',
+ 'src/ui/right-inspector.ts',
+ 'src/ui/sidebar-upper.ts',
+ 'src/ui/splitters.ts',
+ 'src/ui/workbench/workbench-shell.ts',
+ ];
+
+ async function loadManifest() {
+ return JSON.parse(await readFile(resolve(repoRoot, 'build/ui-complexity-manifest.json'), 'utf8'));
+ }
+
+ it('measures exactly the pinned file set', async () => {
+ const manifest = await loadManifest();
+ expect([...manifest.files.map((f) => f.path)].sort()).toEqual([...PINNED_PATHS].sort());
+ });
+
+ it('declares a known class for every file, and no duplicate paths', async () => {
+ const manifest = await loadManifest();
+ const seen = new Set();
+ for (const file of manifest.files) {
+ expect(FILE_CLASSES, `${file.path} class`).toContain(file.class);
+ expect(seen.has(file.path), `${file.path} duplicated`).toBe(false);
+ seen.add(file.path);
+ }
+ });
+
+ it('classifies the three calls a first pass got wrong', async () => {
+ // These were mislabelled as replaceable plumbing in an early draft, which
+ // overstated the baseline the Preact arm had to beat by ~1000 physical lines.
+ const byPath = Object.fromEntries((await loadManifest()).files.map((f) => [f.path, f.class]));
+ expect(byPath['src/ui/left-nav-separator.ts']).toBe('island');
+ expect(byPath['src/ui/nav-sections.ts']).toBe('domain');
+ expect(byPath['src/ui/sidebar-upper.ts']).toBe('domain');
+ });
+
+ it('covers every numbered item of #577\'s evaluation scope that has code today', async () => {
+ // Items 1-3, 5 and 6 exist on main. Item 4 (the right inspector) is what the
+ // control adds, and item 7 (CodeMirror) sits behind the EditorPort seam and
+ // is measured as an island via its adapter, not as shell plumbing.
+ const manifest = JSON.parse(await readFile(resolve(repoRoot, 'build/ui-complexity-manifest.json'), 'utf8'));
+ const covered = manifest.files.map((f) => f.sliceItem || '').join(' ');
+ for (const item of ['1', '2', '3', '5', '6']) {
+ expect(covered, `slice item ${item}`).toContain(item);
+ }
+ });
+});