From f9bed3a6ceb0e6c688939ef7d7ce44124a8163ff Mon Sep 17 00:00:00 2001 From: Marcin Sadowski Date: Tue, 10 Nov 2026 19:47:40 +0100 Subject: [PATCH 1/6] chore: drop four pointless e2e tests and wire every spec into a script --- docs/primitives.md | 18 +++++ package.json | 5 +- test/e2e/graph-navigation.spec.ts | 20 ----- test/e2e/graph-performance.md | 120 ----------------------------- test/e2e/graph-performance.spec.ts | 101 ------------------------ test/e2e/graph-regression.spec.ts | 14 ---- test/e2e/tray-popover.spec.ts | 27 ------- 7 files changed, 22 insertions(+), 283 deletions(-) delete mode 100644 test/e2e/graph-performance.md delete mode 100644 test/e2e/graph-performance.spec.ts 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 8a6446c..a66847e 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 `` (-6 ms, no hover or scroll gain, goes away with 3); filtering type-only imports (the map has no such marker, 94% of relations carry a `#symbol`: needs a format change). - -## Recommended plan - -1. **Scenarios 1 and 2** (one small PR). Verify: All hover at most 55 ms (from 216), select at most 120 (from 273), Web hover at most 55 (from 122), `focusOn` under 1 ms. -2. **Scenario 3 as decided: relations only after a click, none by default.** Verify: All and Web scroll p95 at most 20 ms (from 515 and 392), SVG elements on All under 20,000 (from 33,241), hover GPU under 250 ms (from 1,026), All mount no worse than the same-session baseline (prototype: -22%). -3. **Scenarios 4 and 5.** Verify: a window focus on All costs under 20 ms of main-thread time (from 308) and starts no load when the map is unchanged; typing `service` on All is one redraw, under 250 ms in total (from about 815). -4. **Scenario 6.** Verify: warm `lantern:load` at most 300 ms (from 549), first load after launch under 800 ms (from 1.2-2.9 s), one load per tab open. -5. **Re-measure, then decide on node windowing.** Window the nodes only if Web mount stays above 150 ms or a filter toggle above 100 ms (now 167 and 166). - -Each step is verified by re-running the same harness (patched copy, same project copy, same machine state) and comparing the numbers named above; a figure that moves less than half of the stated gain is a failed step. - -## Result of plan step 2 (click-to-reveal) - -Same harness and project copy, base (`chore/graph-dim-class`, scenarios 1 and 2) against the change, two launches each, interleaved, medians of 5 (scroll: 3 sweeps of 60 steps of 90 px). Machine load average was 5-13 during the runs, so read the mount and click rows as indicative. - -| Metric | Base | Change | Target | -|---|---|---|---| -| All scroll p95 | 633 / 543 ms | 17.5 / 17.2 ms | at most 20 | -| Web scroll p95 | 582 / 547 ms | 17.5 / 17.1 ms | at most 20 | -| SVG elements on All | 33,241 | 17,705 | under 20,000 | -| Hover GPU (3 in+out pairs, All) | 1,121 ms | 0 (no GPU task) | under 250 | -| All mount (tab switch to frame) | 249 / 250 ms | 183 / 167 ms | no worse | -| Web mount | 150 / 150 ms | 117 / 101 ms | no worse | -| All hover to frame | 50 ms | 33 ms (idle floor; nothing is drawn) | - | -| All click (170 edges) to frame | 83-100 ms | 83-100 ms | - | - -## Status: implemented - -All planned scenarios are merged (the platform-tab default was cut). Numbers are from each PR's own harness run on a copy of the project, medians, machine under load. - -| Step | PR | Result | Target | -|---|---|---|---| -| 1. Scenarios 1 and 2: no fade, one dim class, edges indexed by node | #49 | All hover 210 to 50 ms, All select 263 to 83-100, Web hover 133 to 33-50, `focusOn` 8.3 to 0.5-0.9 | 55 / 120 / 55 / under 1: met (one of five All-hover launches read 66.5) | -| 2. Scenario 3: relations only after a click | #51 | see the section above | met | -| 3. Scenarios 4 and 5: skip reload on unchanged focus, debounced search | #52 | window focus 223-231 to 22-24 ms with no load and no redraw; `service` typed: 7 redraws to 1, 601-732 to 208-253 ms | under 20 / under 250: focus missed by about 4 ms (6-9 ms is the hub and review focus listeners, not Lantern), typing met with little margin | -| 4. Scenario 6: load path | #50 | warm `lantern:load` about 530 to 66-74 ms, first load after launch 836-940 to 134-168 ms, one build per tab open (the renderer still sends two IPC calls, the second joins the first or is a warm 66-90 ms build) | 300 / 800 / one: met | - -Behaviour decided on the way: - -- `lantern:load` returns the graph first; the unmapped-file coverage arrives later through `lantern:coverage`. Parsed files are cached in RAM by path, mtime and size; a caller with `fresh` (files changed, agent finished, PR fetched) queues exactly one trailing build. There is no disk cache. -- Window focus compares a stamp (mtime and size of `.lantern/` and the git refs) and reloads only when it differs, or when the last load is over 5 minutes old, because source files are not in the stamp. -- Search redraws the graph once, 200 ms after the last key, and swaps only the stats block; a selected node that the search hides stays in the detail pane until the next full refresh. - -Open: step 5 (re-measure, then decide on node windowing) has not been run on the merged build. Non-blocking review notes: `lanternview.ts`, `lantern.ts` and `features/lantern.ts` are over the 300-line guideline; `loadPrs` is still in the first load stage on projects with a git origin and was not measured. - -## Relation view - -A toggle (top right of the graph once a file is selected, and a button in the side panel detail) redraws the graph as the selected file and its direct relations only: files it takes from on the left, files it sends into on the right, both-way files in a group above, and the file itself in the middle with an arrowed edge to each neighbour. A side with no files is dropped, so a file that takes from nothing sits at the far left and one that gives nothing at the far right. Long lists wrap into up to three sub-columns, grouped by directory (the group above may use up to six). - -Choices: filters, search and the platform tab apply to the neighbours (a neighbour they hide is not drawn); pull request mode keeps its tint on the cards; a neighbour on another platform is a boundary card and a click on it switches platform and re-centres; a click on any neighbour re-centres with a move of 240 ms (instant under `prefers-reduced-motion`); Esc or the toggle leaves, keeping the currently centred file selected; a filter or search that hides the centred file also leaves the view (the normal graph is drawn and it does not come back by itself); a click on empty space does nothing in this view. The hint "Many relations in X" appears in the side panel at 25 or more related files in one column (`DENSE_COLUMN_RELATIONS`), is dismissed for the rest of the session with the cross, and is not shown in pull request mode (that panel replaces the detail). - -Measured on the copy of the project (All tab, `client/index.ts`, 170 relations: 3 sent, 166 both ways), medians of 6, machine under load: - -| Case | Result | -|---|---| -| Enter the view | 85 ms script, 94 ms to frame; 1274 SVG elements (170 cards, 169 edges), page DOM 6402 instead of 21967 | -| Re-centre on a neighbour (16 cards) | 70 ms script, longest animation 240 ms | -| Leave the view | about 150 ms (the normal graph is drawn again, as on a filter change) | -| Normal graph, All scroll p95 | 17.3 ms (lift branch 17.6 ms) | -| Normal graph, click to frame | 116 ms (lift branch 117 ms, same session); still no edges in the DOM until a click | diff --git a/test/e2e/graph-performance.spec.ts b/test/e2e/graph-performance.spec.ts deleted file mode 100644 index 89c5db2..0000000 --- a/test/e2e/graph-performance.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { mkdir, writeFile } from 'node:fs/promises'; -import { readFile } from 'node:fs/promises'; -import { createHash } from 'node:crypto'; -import { cpus, totalmem } from 'node:os'; -import path from 'node:path'; -import { closeGraphHarness, launchGraphHarness } from './support/graph-harness'; - -test('records large Pixi selection latency and renderer resource baselines', async () => { - const fixture = process.env.LANTERN_GRAPH_MAP ? 'real' : 'large'; - const startupMs: number[] = []; - for (let index = 0; index < 2; index += 1) { - const fresh = await launchGraphHarness(fixture); - startupMs.push(fresh.startupMs); - await closeGraphHarness(fresh); - expect(fresh.errors).toEqual([]); - } - const harness = await launchGraphHarness(fixture); - startupMs.push(harness.startupMs); - let caught: unknown; - try { - const page = harness.page; - const session = await harness.app.context().newCDPSession(page); - await session.send('Performance.enable'); - const before = await session.send('Performance.getMetrics'); - const node = page.locator('.lt-pixi-node-access').nth(600); - const samples: number[] = []; - for (let index = 0; index < 6; index += 1) { - samples.push(await node.evaluate(async (button: HTMLButtonElement) => { - const startedAt = performance.now(); - button.click(); - await new Promise<void>((resolve) => { - const settled = () => document.documentElement.dataset.graphTransition === 'settled' - ? requestAnimationFrame(() => resolve()) - : requestAnimationFrame(settled); - requestAnimationFrame(settled); - }); - return performance.now() - startedAt; - })); - if (index % 2 === 0) await expect(node).toHaveAttribute('aria-pressed', 'true'); - else await expect(node).toHaveAttribute('aria-pressed', 'false'); - } - const after = await session.send('Performance.getMetrics'); - const rawSamples = samples.slice(1); - const sorted = [...rawSamples].sort((a, b) => a - b); - const sortedStartup = [...startupMs].sort((a, b) => a - b); - const sourceHash = process.env.LANTERN_GRAPH_MAP - ? createHash('sha256').update(await readFile(process.env.LANTERN_GRAPH_MAP)).digest('hex') - : null; - const metric = (name: string, values: typeof after.metrics) => values.find((entry) => entry.name === name)?.value ?? null; - const report = { - schema: 1, - renderer: 'pixi-webgl', - fixture: { name: fixture, nodes: await page.locator('.lt-pixi-node-access').count(), source: fixture === 'real' ? process.env.LANTERN_GRAPH_MAP : 'synthetic', sha256: sourceHash }, - environment: { - platform: process.platform, - arch: process.arch, - electron: await harness.app.evaluate(() => process.versions.electron), - chromium: await page.evaluate(() => navigator.userAgent), - viewport: await page.evaluate(() => ({ width: innerWidth, height: innerHeight })), - devicePixelRatio: await page.evaluate(() => devicePixelRatio), - cpu: cpus()[0]?.model ?? 'unknown', - logicalCpus: cpus().length, - totalMemoryBytes: totalmem(), - gpu: await harness.app.evaluate(({ app }) => app.getGPUInfo('basic')), - }, - startup: { - name: 'Electron launch to settled graph', - samplesMs: startupMs, - medianMs: sortedStartup[1], - worstMs: sortedStartup[2], - }, - interaction: { - name: 'semantic activation to settled Pixi frame', - warmupDiscarded: samples[0], - samplesMs: rawSamples, - medianMs: sorted[Math.floor(sorted.length / 2)], - p95Ms: sorted[Math.ceil(sorted.length * 0.95) - 1], - worstMs: sorted.at(-1), - }, - resources: { - jsHeapUsedBeforeBytes: metric('JSHeapUsedSize', before.metrics), - jsHeapUsedAfterBytes: metric('JSHeapUsedSize', after.metrics), - domNodesAfter: metric('Nodes', after.metrics), - layoutCountDelta: (metric('LayoutCount', after.metrics) ?? 0) - (metric('LayoutCount', before.metrics) ?? 0), - }, - }; - const output = path.resolve(`test-results/graph-performance/pixi-${fixture}-baseline.json`); - await mkdir(path.dirname(output), { recursive: true }); - await writeFile(output, `${JSON.stringify(report, null, 2)}\n`); - expect(report.interaction.samplesMs).toHaveLength(5); - expect(report.resources.domNodesAfter).toBeGreaterThan(100); - expect(await page.locator('html').getAttribute('data-graph-renderer')).toBe('webgl'); - } catch (error) { - caught = error; - } finally { - await closeGraphHarness(harness); - } - expect(harness.errors).toEqual([]); - if (caught) throw caught; -}); diff --git a/test/e2e/graph-regression.spec.ts b/test/e2e/graph-regression.spec.ts index a9b8e6d..a091720 100644 --- a/test/e2e/graph-regression.spec.ts +++ b/test/e2e/graph-regression.spec.ts @@ -83,17 +83,3 @@ test('shows the production empty state', async () => { await expect(harness.page.locator('.lt-none')).toContainText('No file matches these filters'); await expect(harness.page.locator('.lt-pixi-node-access')).toHaveCount(0); }); - -test('@visual proves the screenshot guard rejects a deliberate mismatch', async ({}, testInfo) => { - test.skip(['all', 'changed'].includes(testInfo.config.updateSnapshots), 'The guard requires an immutable baseline'); - let mismatchDetected = false; - const canvas = harness.page.locator('canvas.lt-pixi-canvas'); - const originalStyle = await canvas.getAttribute('style'); - await canvas.evaluate((element) => { element.style.filter = 'invert(1)'; }); - try { await expect(harness.page).toHaveScreenshot('pixi-overview.png'); } catch { mismatchDetected = true; } - expect(mismatchDetected).toBe(true); - await canvas.evaluate((element, style) => style === null ? element.removeAttribute('style') : element.setAttribute('style', style), originalStyle); - await harness.page.mouse.move(0, 0); - await loadGraphFixture(harness); - await expect(harness.page).toHaveScreenshot('pixi-overview.png'); -}); diff --git a/test/e2e/tray-popover.spec.ts b/test/e2e/tray-popover.spec.ts index 67dfbee..7a73a45 100644 --- a/test/e2e/tray-popover.spec.ts +++ b/test/e2e/tray-popover.spec.ts @@ -11,33 +11,6 @@ async function shot(page: Page, testInfo: TestInfo, name: string) { await page.screenshot({ path: process.env.LANTERN_TRAY_SHOTS ? path.join(process.env.LANTERN_TRAY_SHOTS, file) : testInfo.outputPath(file) }); } -for (const scale of [1, 2]) { - test(`the header logo is the app icon at ${scale}x, on its navy tile and unaffected by the state`, async ({}, testInfo) => { - const h = await launchTrayHarness('460x820', scale); - try { - await h.show(model(manyGroups())); - const logo = h.frame.locator('.head .logo'); - const colours = await logo.evaluate((node) => [...node.querySelectorAll('rect,circle,path,g')].flatMap((part) => [part.getAttribute('fill'), part.getAttribute('stroke')]).filter(Boolean)); - expect(colours).toEqual(expect.arrayContaining(['#040d23', '#f53103', '#ff9646', '#ffd896'])); - expect(await h.page.evaluate(() => devicePixelRatio)).toBe(scale); - await h.page.mouse.move(2, 810); - const file = (name: string) => (process.env.LANTERN_TRAY_SHOTS ? path.join(process.env.LANTERN_TRAY_SHOTS, name) : testInfo.outputPath(name)); - await h.page.screenshot({ path: file(`logo-popover@${scale}x.png`) }); - await logo.evaluate((node) => { - const box = node as HTMLElement; - box.style.width = '256px'; - box.style.height = '256px'; - box.style.borderRadius = '56px'; - }); - await logo.screenshot({ path: file(`logo-enlarged@${scale}x.png`) }); - expect(await h.frame.locator('.head .pill').textContent()).toBe('4 need you'); - expect(h.errors).toEqual([]); - } finally { - await h.close(); - } - }); -} - test.describe.serial('tray popover', () => { let h: TrayHarness; From 89178d42bffc133e24aa2bc5ace5d68c690e4d6f Mon Sep 17 00:00:00 2001 From: Marcin Sadowski <marcin.sadowski95@outlook.com> Date: Tue, 10 Nov 2026 19:47:52 +0100 Subject: [PATCH 2/6] chore: add run folder --- .ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md | 2 ++ .ai/runs/2026-11-10-prune-wire-e2e/PLAN.md | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md create mode 100644 .ai/runs/2026-11-10-prune-wire-e2e/PLAN.md diff --git a/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md b/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md new file mode 100644 index 0000000..87d6bef --- /dev/null +++ b/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md @@ -0,0 +1,2 @@ +# Handoff +Plan unreviewed (unattended). Code done. Orphaned specs fail on their own (macro 13 failed, pr-review 5 failed, lsp esbuild svg loader, text-select skipped); test:graph:typecheck already red on origin/main (graph-pixi getBitmap void). Next: review, map, push. diff --git a/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md b/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md new file mode 100644 index 0000000..2bc1f03 --- /dev/null +++ b/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md @@ -0,0 +1,15 @@ +# Plan: prune and wire e2e + +## Tasks +| # | Step | Status | Commit | +|---|---|---|---| +| 1 | Delete the four pointless tests and graph-performance (spec, md, script) | done | f9bed3a6 | +| 2 | Add test:graph:macro:e2e, test:pr-review:e2e, test:text-select:e2e, test:lsp:e2e | done | f9bed3a6 | +| 3 | Document in docs/primitives.md | done | f9bed3a6 | +| 4 | Lantern map | pending | | + +## Validation +pnpm run typecheck, test:graph:typecheck, lint, pnpm test; each new script run once. + +## Non-goals +Fixing failing orphaned specs, src/ changes, CI, snapshots. From 05b5942413282e0e81f2a0e23d58a68f5b7285ea Mon Sep 17 00:00:00 2001 From: Marcin Sadowski <marcin.sadowski95@outlook.com> Date: Tue, 10 Nov 2026 19:50:03 +0100 Subject: [PATCH 3/6] chore: remove run folder --- .ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md | 2 -- .ai/runs/2026-11-10-prune-wire-e2e/PLAN.md | 15 --------------- 2 files changed, 17 deletions(-) delete mode 100644 .ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md delete mode 100644 .ai/runs/2026-11-10-prune-wire-e2e/PLAN.md diff --git a/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md b/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md deleted file mode 100644 index 87d6bef..0000000 --- a/.ai/runs/2026-11-10-prune-wire-e2e/HANDOFF.md +++ /dev/null @@ -1,2 +0,0 @@ -# Handoff -Plan unreviewed (unattended). Code done. Orphaned specs fail on their own (macro 13 failed, pr-review 5 failed, lsp esbuild svg loader, text-select skipped); test:graph:typecheck already red on origin/main (graph-pixi getBitmap void). Next: review, map, push. diff --git a/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md b/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md deleted file mode 100644 index 2bc1f03..0000000 --- a/.ai/runs/2026-11-10-prune-wire-e2e/PLAN.md +++ /dev/null @@ -1,15 +0,0 @@ -# Plan: prune and wire e2e - -## Tasks -| # | Step | Status | Commit | -|---|---|---|---| -| 1 | Delete the four pointless tests and graph-performance (spec, md, script) | done | f9bed3a6 | -| 2 | Add test:graph:macro:e2e, test:pr-review:e2e, test:text-select:e2e, test:lsp:e2e | done | f9bed3a6 | -| 3 | Document in docs/primitives.md | done | f9bed3a6 | -| 4 | Lantern map | pending | | - -## Validation -pnpm run typecheck, test:graph:typecheck, lint, pnpm test; each new script run once. - -## Non-goals -Fixing failing orphaned specs, src/ changes, CI, snapshots. From 03cff53b4f983ad6a9294ae9c889c26f370f23ed Mon Sep 17 00:00:00 2001 From: Marcin Sadowski <marcin.sadowski95@outlook.com> Date: Tue, 10 Nov 2026 19:50:45 +0100 Subject: [PATCH 4/6] chore: update the Lantern map --- .lantern/lang/en/.lantern.json | 2 +- .lantern/lang/en/test/e2e.lantern.json | 11 +++++------ .lantern/map/.lantern | 2 +- .lantern/map/test/e2e.lantern | 10 +++------- 4 files changed, 10 insertions(+), 15 deletions(-) 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 d6e77e5..4e044bd 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 2c4fa369f4dc +package.json . config 4778f8912f67 > 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 From 9dfb8cf8ab8d89e64ce657977862fc474f9e1cd7 Mon Sep 17 00:00:00 2001 From: Marcin Sadowski <marcin.sadowski95@outlook.com> Date: Tue, 10 Nov 2026 19:51:35 +0100 Subject: [PATCH 5/6] chore: record pull request #32 in the Lantern map --- .lantern/prs/32.pr | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .lantern/prs/32.pr diff --git a/.lantern/prs/32.pr b/.lantern/prs/32.pr new file mode 100644 index 0000000..1c11fc8 --- /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 05b5942413282e0e81f2a0e23d58a68f5b7285ea +#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 From c8995a0e0b72147dc62acc6348d374fcc7c4bc73 Mon Sep 17 00:00:00 2001 From: Marcin Sadowski <marcin.sadowski95@outlook.com> Date: Wed, 11 Nov 2026 13:01:11 +0100 Subject: [PATCH 6/6] chore: update the Lantern map after merging main --- .lantern/map/.lantern | 2 +- .lantern/prs/32.pr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/prs/32.pr b/.lantern/prs/32.pr index 1c11fc8..047cb47 100644 --- a/.lantern/prs/32.pr +++ b/.lantern/prs/32.pr @@ -5,7 +5,7 @@ lantern-pr 1 #branch chore/prune-and-wire-e2e #base main #opened 2026-11-10 -#head 05b5942413282e0e81f2a0e23d58a68f5b7285ea +#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.