docs(claude): carry the live-inspection recipes into the skills

Both were only in the auto-memory store, where they are recalled by
relevance rather than read when the matching skill loads.

measure-startup-requests gains the counterpart to its login-based
capture: timing one module graph by importing it straight off the dev
server, which needs no session. The `.catch` on that import is the whole
trick — an ES graph is fetched and instantiated before any of it is
evaluated, so a module that throws for want of a session still gives a
valid number. The CKEditor figures are kept as the worked example, since
they are what identified the module graph rather than `editor.create()`
as the "first text note is slow" cost.

inspecting-the-running-app gains the `window.glob.appContext` summoning
tip, the NixOS system-chromium executablePath (its downloaded browsers
fail on libX11), a Linux port kill next to the PowerShell one, and the
pitfall that once produced a wrong diagnosis: an async re-render between
the evaluate() that reads computed styles and the screenshot() can wipe
the state being studied, which reads as the CSS never having painted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Elian Doran 2026-08-30 19:26:35 +03:00
parent 437c37d374
commit b92cd6c453
No known key found for this signature in database
2 changed files with 38 additions and 0 deletions

View File

@ -31,17 +31,28 @@ Use Playwright imported by **absolute path**
(`file:///…/node_modules/playwright/index.mjs`) — a script written into the scratchpad cannot
resolve `playwright` by name.
`window.glob` is exposed, so most UI can be summoned without clicking through to it:
`glob.appContext.triggerCommand("showOptions")`, `"openInTreePopup"` (with
`{ noteIdOrPath, hoistedNoteId }`), `"showDeleteNotesDialog"`, …, and `glob.froca` for note lookups.
Fixture gotchas:
- It opens on a **protected** note, so click another note first.
- It runs the **new layout**, so there is no ribbon — the attributes editor opens from the
`… attributes` button in `.status-bar`.
- **On NixOS, Playwright's downloaded browsers fail on libX11.** Launch the system one instead:
`chromium.launch({ executablePath: "/etc/profiles/per-user/<user>/bin/chromium" })` — the same
trick as the untracked `apps/server/playwright.config.nixos.ts`.
## Stopping it
Killing the backgrounded `npx tsx` wrapper leaves the node child alive and still holding the port,
so the next boot fails with "Port 37999 is already in use". Kill the listener:
```bash
fuser -k 37999/tcp # Linux; or: ss -tlnp 'sport = :37999' to find the pid first
```
```powershell
Get-NetTCPConnection -LocalPort 37999 -State Listen | % { Stop-Process -Id $_.OwningProcess -Force }
```
@ -57,3 +68,9 @@ Get-NetTCPConnection -LocalPort 37999 -State Listen | % { Stop-Process -Id $_.Ow
- **A mispositioned or self-dimming fixed-position menu** — walk the ancestors for `transform`,
`filter` and `container-type` rather than reading the stylesheets; any of them creates a
containing block and a stacking context (see "Dropdown menus and the backdrop blur" in `SKILL.md`).
**Transient state can vanish between reading it and screenshotting it.** An async re-render between
the `evaluate()` that dumps computed styles and the later `screenshot()` can wipe the state you are
studying (`fancytree-active`, a hover class, an open menu), which reads as "the CSS never painted"
and has produced a wrong diagnosis before. Re-assert the state *at screenshot time*, and pixel-sample
with `pngjs` (available through the e2e require) rather than eyeballing the image.

View File

@ -37,6 +37,27 @@ node .claude/skills/measure-startup-requests/analyze-requests.mjs diff <before.j
3. Capture again and compare: `analyze-requests.mjs diff baseline.json after.json`.
4. `probe` confirms specific heavy deps stayed off the boot path.
## Timing one module graph, without logging in
The capture above needs a session. To weigh a *single* entry point instead — "what does reaching
CKEditor actually cost?" — skip the login entirely: Vite serves module URLs unauthenticated.
- URL form: `http://localhost:8080/assets/v<version>/@fs/<absolute repo path>/…/file.ts`.
- Navigate a blank page to that origin, `setContent("<html><body>")`, then take `performance.now()`
around `await import(url).catch(() => {})`.
- **The `.catch` is what makes this work.** An ES module graph is fully fetched and instantiated
before any of it is evaluated, so a module whose *evaluation* throws without a session ("Logged in
session not found") still yields a valid download-and-instantiate measurement.
- Constructing an editor in that page needs `{ licenseKey: "GPL" }`, or `create()` throws
`license-key-missing`.
This is how the "first text note is slow" delay was attributed to the module graph rather than to
the editor (dev server, 2026-08): `packages/ckeditor5/src/index.ts` ~290 ms / 216 modules and
`EditableText.tsx` ~355 ms / 328 modules, against `PopupEditor.create()` at 83 ms first and ~25 ms
after. That is why the idle preload (`preloadCommonNoteTypes` in `note_types.tsx`) is the lever —
and why the 428 KB emoji `definitionsUrl` fetch is not a suspect: `EmojiRepository.init()` fires it
without returning the promise, so it never blocks `create()`.
## Interpreting results
- **Dev-mode numbers, not production.** The dev server serves unbundled ES modules (~500+ script