diff --git a/.lantern/lang/en/.lantern.json b/.lantern/lang/en/.lantern.json index 104841e..cee4860 100644 --- a/.lantern/lang/en/.lantern.json +++ b/.lantern/lang/en/.lantern.json @@ -2,7 +2,7 @@ "text": { "CLAUDE.md": "Tells an agent to read the primitives map before changing code", "biome.json": "Configures Biome as a lint-only gate over the source folders, keeping the recommended rules except those switched off because existing code breaks them, with the formatter and import sorting off", - "package.json": "Names the package lantern with its description, repository, homepage and issue links, keywords, MIT licence and Mobile Reality as author, and declares desktop dependencies and development, lint, test, packaging (macOS and Windows) and release commands, including the skill-test harness, the run-cost command, the Flow layout check, the Integrations screen and account choice checks and the tray popover check, the pull request state check against a stub GitHub API, the Skills window check, shipping the lantern command and the licence folder as extra resources (adding Electron's licence files after packaging), marked private so it cannot be published to a registry and naming no entry file of its own because the packaged app's manifest does, and leaving the Electron version to the installed package, with the start command running the dev profile and open:dev launching an already built dev profile without rebuilding, and pinning patched versions of transitive development tooling through pnpm overrides", + "package.json": "Names the package lantern with its description, repository, homepage and issue links, keywords, MIT licence and Mobile Reality as author, and declares desktop dependencies and development, lint, test, packaging (macOS and Windows) and release commands, including the skill-test harness, the run-cost command, the Flow layout check, the Integrations screen and account choice checks and the tray popover check, the pull request state check against a stub GitHub API, the Skills window check, the Macro view, pull request review, text selection and semantic highlighting browser checks, shipping the lantern command and the licence folder as extra resources (adding Electron's licence files after packaging), marked private so it cannot be published to a registry and naming no entry file of its own because the packaged app's manifest does, and leaving the Electron version to the installed package, with the start command running the dev profile and open:dev launching an already built dev profile without rebuilding, and pinning patched versions of transitive development tooling through pnpm overrides", "playwright.config.ts": "Configures serial Electron browser tests and exact visual snapshots", "pnpm-workspace.yaml": "Includes local packages and enables deployable injected workspace dependencies", "tsconfig.json": "Sets strict TypeScript checking for main, preload, shared and renderer sources and declares the @shared, @main and @renderer import aliases", diff --git a/.lantern/lang/en/test/e2e.lantern.json b/.lantern/lang/en/test/e2e.lantern.json index 6125363..ad4d98e 100644 --- a/.lantern/lang/en/test/e2e.lantern.json +++ b/.lantern/lang/en/test/e2e.lantern.json @@ -4,21 +4,20 @@ "graph-macro-areas.spec.ts": "Checks in the real app that the Macro view opens on the areas map with no layout to choose, enters one with a double-click and leaves it with Escape, Backspace or the breadcrumb, goes sideways to another area from the panel and keeps the entered area across Files, and that a pull request lights the areas it touches and the reading marker moves between areas while stepping.", "graph-macro-systems.spec.ts": "Checks on the evaluation project at 1600 by 1000 and 1280 by 800 that the outside systems list is always there under the map with every system and its count, filters to what a clicked block talks to with Show all, lights every area and foundation entry that talks to a clicked system with counts and a side panel grouped by area and feature, is cleared by a second click or Escape, never clips text, and can be driven with the keyboard; saves screenshots.", "graph-macroview.spec.ts": "Drives the areas map in the real app on three projects at two window sizes: every area fits above the outside systems list, no page scroll or zoom, marks at the right zoom, keys, wheel, breadcrumb, the Macro view mounting once, and screenshots.", - "graph-macro-foundation.spec.ts":"Checks in the real app, on the evaluation project, that selecting a foundation tile lists what it uses and where its platform has files and lights its partners on the areas map, and that an area shows its platforms.", + "graph-macro-foundation.spec.ts": "Checks in the real app, on the evaluation project, that selecting a foundation tile lists what it uses and where its platform has files and lights its partners on the areas map, and that an area shows its platforms.", "graph-macro-legend.spec.ts": "Checks in the real app that the Macro legend is short, fits the side panel, and that its swatches (selected, uses, To assign) are painted exactly like the shapes on the map, and screenshots the areas map and flat keys.", "graph-macro-platform-uses.spec.ts": "Checks in the real app that of two bots of one kind the accounts area is lit strongly for the one with files there and as uses for the one that only uses it, with the legend drawing it the same solid way, lists both in the side panel, lights nothing as uses at rest and screenshots each.", "graph-macro.spec.ts": "Checks the macro view in the real canvas: the counts drawn at rest, readable names, selection, Escape and the way back to the file graph.", "graph-navigation.spec.ts": "Checks graph zoom, panning, right-drag, overscroll, focus and input behaviour in Electron", - "graph-performance.spec.ts": "Measures large-graph startup, selection latency and renderer resource baselines", "graph-pixi.spec.ts": "Checks WebGL rendering, retained tile text across zoom, interaction and failure recovery", "graph-regression.spec.ts": "Guards graph appearance, selection, search and empty states against regressions", - "tray-popover.spec.ts": "Drives the menu bar popover in its harness with synthetic projects in every state: urgency order, a long name and path, nothing needing you, the empty state, the More panel with and without notifications, arrow keys, Enter and Escape, pointer selection, a live update, a scrolling list, the stopped spinner when motion is reduced and the header logo in the app icon's colours at 1x and 2x.", + "tray-popover.spec.ts": "Drives the menu bar popover in its harness with synthetic projects in every state: urgency order, a long name and path, nothing needing you, the empty state, the More panel with and without notifications, arrow keys, Enter and Escape, pointer selection, a live update, a scrolling list, and the stopped spinner when motion is reduced.", "skills-extras.spec.ts": "Drives the Lantern skills tab in Electron over real files: the role selector, scope badges, search, attach with a click and with Enter, moving between scopes, detaching with Delete, the project file kept out of git, the narrow stacked layout and the main window with only the global scope, and captures screenshots when asked", "integrations-bindings.spec.ts": "Adds three accounts of each provider to the Integrations harness and walks two projects and a brand-new one with no origin remote through choosing, re-choosing while pinned, unlinking and reopening the dialog, a Bitbucket account chosen on a GitHub project, an account the environment shadows, plus the button that jumps from the accounts to the projects and the status bar icon naming the chosen account", - "integrations.spec.ts":"Drives the Integrations dialog, opened from its button, at 1280x800 and 700x640 through the real IPC against a stub provider server: adding and refusing tokens, two accounts of one provider, the project tile grid and its detail step with twelve projects, a project's account choice, expiry warnings, removing, a provider icon in the status bar that reopens the dialog on its project, and no cut-off text or file-token wording", - "lsp-semantic.spec.ts":"Checks real semantic highlighting through production preload and IPC in code and diff previews", + "integrations.spec.ts": "Drives the Integrations dialog, opened from its button, at 1280x800 and 700x640 through the real IPC against a stub provider server: adding and refusing tokens, two accounts of one provider, the project tile grid and its detail step with twelve projects, a project's account choice, expiry warnings, removing, a provider icon in the status bar that reopens the dialog on its project, and no cut-off text or file-token wording", + "lsp-semantic.spec.ts": "Checks real semantic highlighting through production preload and IPC in code and diff previews", "pr-host-state.spec.ts": "Loads the pull requests of a GitHub-origin project through the production host status against a stub API and checks the strip shows the open, the squash-merged and the declined one as the host reports them, saving screenshots on request", - "pr-review-demo.spec.ts":"Exercises ordered PR review across the file list, the file and diff pane and the graph", + "pr-review-demo.spec.ts": "Exercises ordered PR review across the file list, the file and diff pane and the graph", "pr-review-layout.spec.ts": "Checks the three review panes fit a laptop window and that the dividers move by keyboard and pointer", "text-select.spec.ts": "Checks in a real Chromium that the readable review and Flow text computes to selectable while the chrome stays unselectable, and that a drag-select on a Flow step does not pick it; skips when no Chromium can be launched", "tsconfig.json": "Configures type checking for browser tests and their production dependencies", diff --git a/.lantern/map/.lantern b/.lantern/map/.lantern index 5743885..f13f1c6 100644 --- a/.lantern/map/.lantern +++ b/.lantern/map/.lantern @@ -3,7 +3,7 @@ lantern 1 CLAUDE.md . skill f21d73adfc12 biome.json . config 216278473939 -package.json . config 8cc76988675c +package.json . config 887c01fc0f21 > scripts/e2e-delegate/run.mjs > scripts/esbuild.mjs > scripts/fuses.js diff --git a/.lantern/map/test/e2e.lantern b/.lantern/map/test/e2e.lantern index 128e96c..b64cf51 100644 --- a/.lantern/map/test/e2e.lantern +++ b/.lantern/map/test/e2e.lantern @@ -38,11 +38,7 @@ test/e2e/graph-macro.spec.ts . test 0f4c5055ed0f = test/e2e/support/graph-harness.ts = test/fixtures/lantern-macro-own-view.json -test/e2e/graph-navigation.spec.ts . test 7ed46145f408 -#feature file-graph - = test/e2e/support/graph-harness.ts - -test/e2e/graph-performance.spec.ts . test ae8c54d65587 +test/e2e/graph-navigation.spec.ts . test ba58988b46e7 #feature file-graph = test/e2e/support/graph-harness.ts @@ -50,7 +46,7 @@ test/e2e/graph-pixi.spec.ts . test 0140b656f39e #feature file-graph = test/e2e/support/graph-harness.ts -test/e2e/graph-regression.spec.ts . test bb415efaaf2b +test/e2e/graph-regression.spec.ts . test 6581982470cc #feature file-graph = test/e2e/support/graph-harness.ts @@ -68,7 +64,7 @@ test/e2e/skills-extras.spec.ts . test 55b79a7cb294 #feature skill-installer = test/e2e/support/skills-extras-harness.ts#launchSkillsHarness -test/e2e/tray-popover.spec.ts . test f949568c0813 +test/e2e/tray-popover.spec.ts . test 07d60516c636 #feature session-monitoring = test/e2e/support/tray-popover-harness.ts#launchTrayHarness < test/fixtures/tray-groups.ts#manyGroups diff --git a/.lantern/prs/32.pr b/.lantern/prs/32.pr new file mode 100644 index 0000000..047cb47 --- /dev/null +++ b/.lantern/prs/32.pr @@ -0,0 +1,51 @@ +lantern-pr 1 +# 32 +#title chore: prune pointless e2e tests and wire every spec into a script +#url https://github.com/MobileReality/lantern/pull/32 +#branch chore/prune-and-wire-e2e +#base main +#opened 2026-11-10 +#head d296953325cb14ab5726ace142374b2b7306dad8 +#describe what Removes four e2e tests that proved nothing about the app and gives every remaining Playwright spec a package.json script. +#describe before Twelve specs were run by no script. +#describe after Every test/e2e spec is named by a script, and the four new ones are listed in docs/primitives.md. +#describe verified typecheck, lint and pnpm test green; the newly wired scripts were each run once and several fail on their own, reported in the PR. +~ docs/primitives.md +~ package.json +~ test/e2e/graph-navigation.spec.ts +~ test/e2e/graph-regression.spec.ts +~ test/e2e/tray-popover.spec.ts +- test/e2e/graph-performance.md +- test/e2e/graph-performance.spec.ts +#order package.json +#order test/e2e/graph-performance.spec.ts +#order test/e2e/graph-performance.md +#order test/e2e/graph-regression.spec.ts +#order test/e2e/graph-navigation.spec.ts +#order test/e2e/tray-popover.spec.ts +#order docs/primitives.md +#reason docs/primitives.md Agents find regression checks here, so the new scripts are listed with when to run them. +#reason package.json Specs no script ran stayed unrun and rotted, so each now has a named script. +#reason test/e2e/graph-navigation.spec.ts The removed test asserted harness window state and duplicated the zoom checks in graph-pixi. +#reason test/e2e/graph-regression.spec.ts The removed test checked Playwright's own screenshot comparison, not the app. +#reason test/e2e/tray-popover.spec.ts The removed logo tests compared SVG fill strings and saved screenshots nobody compared. +#reason test/e2e/graph-performance.md It documented the removed performance baseline, which no longer exists. +#reason test/e2e/graph-performance.spec.ts It had no latency budget and could not fail on a slowdown, so it gave false assurance. +#story Every browser check has a command +#step actor A developer +#step action runs a named check script +#file package.json +#file docs/primitives.md +#step subject the macro, review, selection or highlight specs +#below interaction Can run any e2e spec from a script found in the docs +#below consequence Twelve specs were run by nothing => Every spec belongs to a script +#story Tests that proved nothing are gone +#step actor A developer +#step action runs the graph and tray checks +#file test/e2e/graph-regression.spec.ts +#file test/e2e/graph-navigation.spec.ts +#file test/e2e/tray-popover.spec.ts +#file test/e2e/graph-performance.spec.ts +#step subject the remaining specs +#below interaction Reads only checks that can fail on a real regression +#below consequence Four checks passed whatever the app did => They are removed diff --git a/docs/primitives.md b/docs/primitives.md index 144cdb1..866ac3c 100644 --- a/docs/primitives.md +++ b/docs/primitives.md @@ -46,6 +46,24 @@ layout, rendering or navigation in the renderer; `pnpm run test:graph:visual` ad snapshots (update them with `test:graph:visual:update` only when the change is meant to move pixels, and say so in the PR). Not part of `pnpm test`. +### `pnpm run test:graph:macro:e2e` +`test/e2e/graph-macro*.spec.ts` and `graph-macroview.spec.ts`, Playwright against the same harness. Use +when a change touches the macro (areas, systems, platform uses, foundation, legend) view of the graph. +Not part of `pnpm test`. + +### `pnpm run test:pr-review:e2e` +`test/e2e/pr-review-*.spec.ts`, Playwright against the same harness. Use when a change touches the pull +request review screen (comments, explain, layout, regions, the demo). Not part of `pnpm test`. + +### `pnpm run test:text-select:e2e` +`test/e2e/text-select.spec.ts`. Use when a change touches text selection in the renderer. Skips without a +Playwright Chromium. Not part of `pnpm test`. + +### `pnpm run test:lsp:e2e` +Compiles, then runs `test/e2e/lsp-semantic.spec.ts` in Electron: semantic highlighting through the real +preload and IPC. Use when the embedded editor's highlighting or its LSP bridge changes; `test:lsp` is the +vitest side of the same area. Not part of `pnpm test`. + ### `pnpm run test:lsp` Compiles, then runs the LSP and semantic-highlight integration tests. Use when the embedded editor, tsserver or highlighting changes. diff --git a/package.json b/package.json index 9c4bddf..7459f7e 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,10 @@ "test:tray:e2e": "playwright test test/e2e/tray-popover.spec.ts", "test:pr-host:e2e": "playwright test test/e2e/pr-host-state.spec.ts", "test:skills:e2e": "playwright test test/e2e/skills-extras.spec.ts", - "test:graph:perf": "playwright test test/e2e/graph-performance.spec.ts", + "test:graph:macro:e2e": "playwright test test/e2e/graph-macro.spec.ts test/e2e/graph-macro-areas.spec.ts test/e2e/graph-macro-foundation.spec.ts test/e2e/graph-macro-legend.spec.ts test/e2e/graph-macro-platform-uses.spec.ts test/e2e/graph-macro-systems.spec.ts test/e2e/graph-macroview.spec.ts", + "test:pr-review:e2e": "playwright test test/e2e/pr-review-comments.spec.ts test/e2e/pr-review-demo.spec.ts test/e2e/pr-review-explain.spec.ts test/e2e/pr-review-layout.spec.ts test/e2e/pr-review-regions.spec.ts", + "test:text-select:e2e": "playwright test test/e2e/text-select.spec.ts", + "test:lsp:e2e": "pnpm compile && playwright test test/e2e/lsp-semantic.spec.ts", "demo:pr-review": "node scripts/pr-review-demo.mjs", "coverage": "vitest run --coverage", "start": "node scripts/esbuild.mjs && LANTERN_PROFILE=dev env -u ELECTRON_RUN_AS_NODE electron out", diff --git a/test/e2e/graph-navigation.spec.ts b/test/e2e/graph-navigation.spec.ts index c0fc685..2d7dca9 100644 --- a/test/e2e/graph-navigation.spec.ts +++ b/test/e2e/graph-navigation.spec.ts @@ -10,26 +10,6 @@ test.afterAll(async () => { try { await closeGraphHarness(harness); } finally { expect(errors).toEqual([]); } }); -test('keeps the Electron harness hidden while screenshots and input work', async () => { - test.skip(process.env.LANTERN_GRAPH_VISIBLE === '1', 'Hidden-window assertions apply only to the default background harness'); - const page = harness.page; - expect(await harness.app.evaluate(({ BrowserWindow }) => { - const window = BrowserWindow.getAllWindows()[0]; - return { visible: window.isVisible(), focused: window.isFocused() }; - })).toEqual({ visible: false, focused: false }); - const input = page.getByRole('textbox', { name: 'Graph zoom percentage' }); - await input.fill('125%'); - await input.press('Enter'); - await expect(input).toHaveValue('125%'); - await page.getByRole('button', { name: 'Zoom out' }).click(); - await expect(input).toHaveValue('115%'); - expect((await page.screenshot()).byteLength).toBeGreaterThan(0); - expect(await harness.app.evaluate(({ BrowserWindow }) => { - const window = BrowserWindow.getAllWindows()[0]; - return { visible: window.isVisible(), focused: window.isFocused() }; - })).toEqual({ visible: false, focused: false }); -}); - test.describe('pixi', () => { test.beforeEach(async () => { await harness.app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1100, 760)); diff --git a/test/e2e/graph-performance.md b/test/e2e/graph-performance.md deleted file mode 100644 index 5e3b85e..0000000 --- a/test/e2e/graph-performance.md +++ /dev/null @@ -1,120 +0,0 @@ -# Lantern graph performance - -> Historical SVG baseline. The current architecture graph uses Pixi/WebGL; these figures describe the earlier SVG implementation. - -Why the Lantern graph is slow on a map with thousands of relations, measured on `main` (5ac1a84), and which fix to do first. - -## How it was measured - -- **Map:** the real `nft-reality-platform` `.lantern`: 2,886 records, 7,768 relations (3,269 `<`, 2,793 `>`, 1,706 `=`; 329 point outside the repo), 1,249 map files, 5 platform tabs plus Shared. The whole project (1.2 GB, without `node_modules`, `.git`, `dist`, `build`, `.next`) was copied to scratch and opened there; the real project was never opened or written. Node counts below exceed record counts because boundary and external boxes are nodes too. -- **App:** a patched copy of a fresh `pnpm compile` build (own ports, user-data dir and HOME, scan/notifier/mic stubbed), timers around the load phases (main) and around layout, columns, edges, nodes and side panel (renderer), driven through the debug `/eval` route. CDP `Performance.getMetrics` gave script/style/layout, CDP `Tracing` gave paint, raster and GPU. Prototypes are flags in that copy only; nothing here is in the repo. -- **Machine:** Apple M3 Pro (11 cores, 19 GB), macOS 26.5.1, Electron 33.4.11, window 1500x950 at DPR 1, normal desktop load (the user's Lantern, VS Code and Slack open, load average about 3), `caffeinate` on. -- **Method:** medians of 5 runs (load and mount 3; scroll 3 sweeps of 60 steps of 90 px). "To frame" = action until the second `requestAnimationFrame` after it; idle floor 33 ms. All and Web rows are medians of three fresh launches (All tab switch 303, 283, 284 ms; hover 239, 209, 216; select 273, 252, 299; Web scroll p95 353, 392, 541), the other rows of one or two launches. Mount also drifts by up to about 35% (281 vs 383 ms for the same build; a step in layout time from about 50 to 145 ms that can hit mid-session), so prototypes are compared only with a baseline measured in the same state and the plan re-measures with baseline and change interleaved. - -## Before (ms unless noted) - -| Tab | Nodes | Edges | SVG elements | Mount | Hover | Select | Filter toggle | Key in search | Scroll p95 | -|---|---|---|---|---|---|---|---|---|---| -| All | 2,971 | 7,768 | 33,241 | 284 | 216 | 273 | 250 | 83 | 515 | -| Web | 1,732 | 4,362 | 19,299 | 167 | 122 | 162 | 166 | 67 | 392 | -| API | 1,338 | 3,051 | 13,259 | 134 | 109 | 121 | 131 | 52 | 96 | -| Slack bot | 162 | 362 | 1,646 | 49 | 34 | 34 | 33 | 33 | 18 | -| Teams, Infra, Shared | 18-66 | 11-97 | 140-575 | 33 | 33 | 33 | 33 | 33 | 18 | - -DOM: 37,491 elements on All (3,935 in the side panel). Memory is not the problem: renderer 317 MB on a tiny tab, 405-447 MB on the big tabs, GPU 91 to 155 MB, JS heap 15 MB. - -**Load (main process; the 2.8 MB payload is the same for every tab):** `JSON.stringify` 5-7 ms, IPC overhead about 35 ms. Warm `lantern:load` median 549 ms: read and parse 1,249 map files 200, source enumeration 141, lang texts and features 170, stat 14, PR records 15. First load after launch 1.2, 2.1 and 2.9 s on three launches, of which enumeration 540-745 ms and lang texts 455-1,690 ms; the tab open and the window-focus handler both call `lantern:load`, so two loads run at once. Reopening Lantern on All: 930 ms to the first frame (load 620 plus mount). - -**Where the time goes on All (Tracing):** - -| Action | Script | Style | Layout | Paint+layerize+commit | Compositor thread | GPU | -|---|---|---|---|---|---|---| -| Mount (per tab switch) | 252 | 80 | 65 | 25 | 5 | 178 | -| Hover in+out (3 pairs) | 105 | 618 | 12 | 290 | 1,730 | 1,026 | -| Scroll (180 frames) | 5 | 0 | 0 | 2,079 | 624 | 25,334 | - -Mount script is 252 ms: graph build 82 (layout 10, columns 12, edge paths 3, path elements 14, arrowheads 3, titles 6, nodes 24, listeners 4), side panel 24, forced reflow from `side.scrollTop` about 117, the rest 29. JS is not the bottleneck. The cost is the browser restyling all 7,768 edges on every hover and select (each also runs a 0.12 s opacity transition on the compositor) and rastering 7,768 long strokes with arrowhead markers on every scroll. - -## Scenarios, ranked by gain per effort (prototypes on All, N=5-7, same-session baselines) - -| # | Scenario | Measured gain | Effort | Risk | What the user sees | -|---|---|---|---|---|---| -| 1 | Remove the opacity transition on `.lt-edge`/`.lt-node` | hover 220 to 67, select 252 to 154; Web hover 125 to 50 | S | S | Dimming is instant, no 0.12 s fade | -| 2 | Focus without per-edge DOM writes: edges indexed by node, dim via one class on the layer, markers only on hot edges (with 1) | hover 67 to 50, select 154 to 100, `focusOn` 8 to 0.6 ms | S | S | Same | -| 3 | Draw only the hovered/selected node's edges, plus a dim bundled hint (first edge per pair of depth-3 directories, 403 paths) | scroll p95 490 to 18, hover 227 to 50, select 360 to 167-183, mount 383 to 300, SVG 33k to 18.5k, hover GPU 1,026 to 186; Web scroll p95 339 to 17 | M | M | Overview is bundles, not every line; real bundles need aggregated geometry and a tooltip story | -| 4 | Skip redraw and reload when nothing changed (window focus) | window focus on All (a dispatched `focus` event, 5 runs) blocks the main thread 308 ms and starts a 0.5 s load each time | S | S | None | -| 5 | Debounce search; update only the changed side-panel block | `service` typed on All is about 815 ms of redraws (83 ms per key, 233 first); `drawSide` is 24-33 ms of every select | S | S | Results after a short pause | -| 6 | Load path: parse cache keyed by mtime, enumeration and lang texts after the first payload, one load at a time | not prototyped; removable from the critical path: enumerate + lang texts = 311 of 549 ms warm, 1.0-2.4 s of the 1.2-2.9 s cold | M | S | Faster open; "unmapped" count arrives a moment later | - -### Decision on scenario 3 (product owner) - -Relations are drawn only after the user **clicks** an element. By default the graph shows nodes and no relation lines: no bundled hint, and hover does not draw relations (it may highlight the node itself). Reason: all relations at once are not readable, so the overview gains nothing from drawing them. - -What changes in this document: - -- Scenario 3 becomes **click-to-reveal**: effort S and risk S, because the aggregated bundle geometry, its tooltip and the "first edge per directory pair" hint are dropped. -- Expected gain is at least the prototype's, since the prototype still drew 403 hint paths and this draws none by default: SVG elements on All fall to roughly the node and column count. This is an expectation, to be confirmed by the harness in plan step 2; it is not a measurement. -- The first version's scenarios 6 (arrowheads only on hot edges) and 7 (hide external edges by default) are cut: with click-to-reveal only the clicked element's edges exist, so every drawn edge can keep its arrowhead and external edges appear only with them. The platform-tab default is also cut, so the load path is now scenario 6. -- Scenarios 1 and 2 still matter for the click and the dim/highlight of the clicked element; scenarios 4, 5 and 6 are unaffected. - -**Dropped, with numbers:** Canvas 2D for edges (1,698 of 7,768 long curves intersect the first viewport; redraw about 100 ms per frame, scroll p50 102 ms vs 58 for SVG, 565 ms for a full draw); viewport virtualisation and `content-visibility` (not available for SVG children; node windowing is at most the node share of mount, revisit after 3); Web Worker or main-process graph preparation (layout 10, edge paths 3, index 2 ms: about 5% of mount); event delegation (listeners 4.4 ms); removing edge `