Skip to content

Viewer M1: verification loop, claim-first Inspector, freshness in place, legacy cleanup - #15

Merged
realmyang merged 10 commits into
mainfrom
viewer-m1-loop
Oct 2, 2026
Merged

realmyang merged 10 commits into
mainfrom
viewer-m1-loop

Conversation

@realmyang

Copy link
Copy Markdown
Owner

Milestone M1 of the UI/UX direction the owner approved on 2026-10-01: tighter checking of claims against source, an Inspector that shows the claim, and removal of dead analyzer code. Diagram shapes do not change; the geometry golden is byte-identical. There are no pilot records here, so any merge method is fine.

What changes for a reader

  • Click selects, it no longer opens. A click on a card, connection, finding or Outline row selects it and shows its claim. Enter, double-click or the Inspector's Open link opens the cited source beside the diagram, with the whole cited range selected and highlighted, and keyboard focus stays on the diagram. Alt+Enter opens the source and moves focus to it. Notebook citations open with the cell selected.
  • Stale evidence is marked in place. Cards, connections, findings and quotes that cite a changed or missing file get a warning mark. Their Open buttons are disabled with the reason, and the status bar shows e.g. "1 of 2 cited files changed". There is no green "all good" mark.
  • Workspace-root hint. When cited files are "missing" but exist one folder down with their published hashes, the panel says so and offers Add folder to workspace or Open folder. This is the parent-folder case from the Stage 1 review; the hint never substitutes a path. After any extension restart in a window (Add folder, Restart Extension Host, Save Workspace As, an extension update), dead diagram tabs are replaced in place.
  • The Inspector shows the claim.
    • The model's full explanation (detail) and the phase name (not its id). The title is no longer repeated.
    • Basis shown once, with a sentence only for inferred or unresolved claims.
    • Each finding's suggestion, under "What to change". It was hidden by a leftover analyzer CSS rule.
    • Document-wide limitations listed once, in Details.
    • A caption that a matching quote shows the lines exist unchanged, not that they support the claim.
  • Notebook cells print the number the evidence records (0-based, like the model's labels), as "cell 34, line 5".

Cleanup

Dead analyzer UI is removed: the diff overlay, pipeline chooser, answers card, fix/suppress/rule panels, group-by-file and legacy branches and types. revealNode/revealIssue and restoreState are kept.

Before After
TypeScript, webview/src 26,061 lines 18,499 lines
Stylesheets 5,428 lines 3,986 lines
dist/mlview.js 365 KB 286 KB

Developer tool

webview/tools/screenshots/ is an opt-in headless-Chrome screenshot harness. It uses Chrome's --remote-debugging-pipe (so it works on Node 20) and a simulated host. Its default inputs are the repo sample and a synthetic document. It is not a CI gate, but its message-parsing test runs in npm test.

Checks

  • sh scripts/e2e.sh --skip-npm-install: E2E OK: 15 exercised gates (pytest 915 passed; webview 155/155; extension 302/302). Node v26.4.0, Python 3.13.15.
  • Three independent model reviews (behaviour, cleanup integrity, accessibility/copy) found 13 issues. All were fixed with regression tests that fail without the fix.
  • Live check, isolated VS Code 1.139 Extension Development Host (throwaway profile, driven over the DevTools protocol, not the owner's VS Code):
    • opening a diagram, click-selects, Enter-opens with range highlight and focus kept, Alt+Enter, a notebook cell, stale marks after editing a cited file;
    • the parent-folder hint, and Add folder in single-folder and multi-root windows;
    • Restart Extension Host, Save Workspace As, VS Code's own Add Folder, and Reload Window (front and background tabs).
    • These passed after the fixes in this PR, re-checked by an independent verifier. The restart-recovery guards are pinned by tests that fail with their guard removed (mutation check).

These checks show behaviour, not usability or semantic accuracy. An extension install or update was not tried live. Pilot-01 reviews keep using the frozen candidate VSIX.

Known limits (documented):

  • A dead diagram tab behind other tabs is replaced when brought to the front, after about half a second of blank page.
  • A diagram put back after an extension restart starts with a fresh view; Reload Window keeps selection and zoom.

🤖 Generated with Claude Code

realmyang and others added 10 commits October 1, 2026 19:16
…hness in place, root hint)

- Source jumps open the cited file beside the diagram with focus kept in
  the diagram, select the whole cited range and highlight it with a theme
  decoration that moves with the next jump and is cleared when the panel
  closes. Alt+Enter (or Alt+click on an Open link) moves focus to the
  editor. Notebook citations select and reveal the cell, and highlight its
  lines when the cell editor is ready within 500 ms. A jump into a file
  whose bytes match the last validation skips the full revalidation.
- A click on a card, connection, finding or Outline row only selects it;
  Enter, a double-click and the Inspector's Open links open the source.
  Shortcut sheet, legend and docs updated.
- The stale frame now carries [{path, reason}]. Cards, connections,
  findings and quotes citing a stale file are marked (icon and words, no
  colour for unchanged files), stale Open links are disabled with the
  reason, and the status bar counts the files. After the mount the host
  banner is drawn as a notice under the header; "checking" goes to the
  status bar.
- When most tracked files are missing from the root but exist with the
  published hashes under the artifact's folder or a parent below the root,
  the banner says so instead of "no longer match" and offers Add folder to
  workspace / Open folder. Panels revalidate on workspace folder changes.
  No path is ever substituted.

No contract change, no new setting, geometry golden unchanged. Checked by
local tests only (webview 130, extension 281); not checked in live VS Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Inspector shows a step's full authored detail as a paragraph under
  the title, the phase label (never the id) and the kind. The mono line that
  repeated the title is gone; the basis appears once (no Attributes table
  for authored steps; a connection's title is its authored label without
  the " · basis" suffix), with one sentence for inferred or unresolved.
- Findings show their suggestion as "What to change" in the Inspector and
  the expanded Findings row. The analyzer-era workflow.css rule that hid
  .mlv-insp__fix is removed; no label is drawn when there is no suggestion.
  A step's findings now come before its quotes.
- Document-wide limitations are listed once, in the header Details; the
  Inspector shows "N document-wide limitations apply. Show", which opens
  Details at the list and moves focus there.
- A caption under the evidence heading says a matching quote shows the lines
  are unchanged since publishing, not that they support the claim.
- A card's accessible name (and the selection announcement) ends with the
  first sentence of the claim, at most 160 characters, and names the phase
  by label.
- Authored notebook cells print as recorded, counted from 0
  ("cell 34, line 2"), matching the evidence, the helper's --cell, the
  extension and the model's own labels; author labels are untouched.
- The phase chip tooltip says what a click does; a connection's hover card
  is titled with its authored label.

The projection's card inputs (sublabel, attrs={basis}, the edge label
suffix) are unchanged; MLNode.detail/phaseLabel and MLEdge.authoredLabel
are new fields that layout does not read. Geometry golden byte-identical.
New webview/test/inspector-content.test.mjs loads the shipped stylesheet
into jsdom and checks visibility by computed style; three existing tests
updated for the deliberate wording and cell-number changes. Checked by
local tests only (webview 143, extension 281); not checked in live VS Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the analyzer-era viewer modules that no authored document reaches:
the diff overlay (diff/overlay, adopt, changed, ui/diffbar), the pipeline
chooser (ui/pipelinechooser, scope/pipelines and the `pipeline:` scope
kind), the answer card (ui/answers), the rollup reader (rollup/rolled),
resolved config (config/resolved), rule fixes, suppression and rule docs
(ui/fixes, ui/suppress, ui/ruledocs and the trust disclosures in
ui/evidence), the chrome banners (ui/chromebanners), the standalone half of
bridges.ts with export/download and the standalone theme switch, and
"group by" (ui/railgroup, dropped as the owner accepted). The call sites
that only served them go too, with the matching types and the ViewState
keys railGroupBy, answersOpen, diffOnly and pipelineChosen (an old saved
state that still carries them is ignored).

The live part of rollup/rolled.ts, the weight of a merged cable, moves to
render/weight.ts; routeWeight is now ids.length || 1, which is what it
returned for every authored edge (no authored edge carries a weight).

Checks (local only): both geometry-golden digests unchanged; a jsdom tour of
39 authored documents (shakedown artifacts, the sample, the skill example
and the dev artifacts), 6454 steps, records the same DOM, ViewState and
posted frames (SVG export included) before and after, leaving out only the
removed elements, each of which was hidden or display:none under the
shipped stylesheet. TypeScript 26061 -> 20173 lines; dist/mlview.js
364619 -> 303850 bytes. Webview tests 143 -> 142 ("file groups report the
weakest authored basis" exercised group by file); two tests updated for the
removed chooser, banners and grouping. Extension tests 281 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every node the viewer draws comes from normalizeWorkflow, so the branches
for analyzer graphs, and the fields only those graphs carried, could not be
reached. Removed:

- Frames: the viewer no longer posts selectNode, scopeChanged, askAssistant
  or action (the host has no handler for any of them), and no longer
  dispatches setFilter, setScope, requestExport or cursorHint (the host
  never sends them). revealNode, revealIssue and restoreState stay.
- State: the loading skeleton, App.error, the "only changed" chip and
  filter, showSuppressed and rule-code filters (saved states that still
  carry those keys are read and the keys ignored).
- Types: ghost, dynamic, confidence, fqn, ports, stage evidence and the
  authored flag on nodes; suppression, change, why and confidence on
  findings; file, line, count, codes and scope on notes; frameworks,
  filesFailed and stats.truncated; the canReanalyze, canExport and
  canAskAssistant capabilities.
- Code: the notebook flat-line cell mapping and out-of-order caveat,
  the concern scope presets, ghost pruning in the projection, the
  relatedLocs connector guess, the coverage chip vocabulary
  (chromenotes.ts) and the analyzer-only Inspector sections (Ask, Ports,
  Why this stage, Attributes); unused exports.

Three visible changes: a merged cable's tooltip no longer mentions the
retired rollup, its accessible name no longer repeats the merge count as a
weight, and a saved state carrying showSuppressed no longer shows "Clear
filters" on load.

Checked: geometry golden byte-identical; a jsdom tour of 39 authored
documents (6454 steps, DOM, ViewState, posted frames and SVG export) is
identical to the previous commit once the retired frames and the three
changes above are set aside, with no errors. Webview 142/142, extension
281/281.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stylesheets still carried the retired analyzer viewer: the answer card,
the diff overlay and fix disclosure, the rail's group headers, suppression,
baseline and change chips, confidence chips, evidence weights and rule
cards, the banners, the standalone theme switch, the loading skeleton, the
pipeline chooser, ghost, low-confidence and dynamic cards, the rolled-up
card, the clean-state caveat and attribute tables, and the block that hid
analyzer controls in an authored document.

- Deleted chromeanswers.css, diff.css, railevidence.css and railgroups.css.
  Their two live rules move to the end of the layer before them, so the
  cascade order is unchanged: the legend's scroll shadow (chromepanels.css)
  and the wrapping finding meta line (rail.css).
- rollup.css becomes weight.css and keeps only the weighted cable.
- Dead rules removed from chrome, chromepanels, chromestates, export (print
  list), node, rail, scope and workflow.

Each removed selector was checked twice: no TypeScript source creates its
class or attribute, and a jsdom tour of 39 authored documents never matched
it. Computed styles of every element at 195 points of that tour are
identical before and after, apart from the retired --mlv-diff-* and
--mlv-fix tokens nothing reads. mlview.css goes from 82,027 to 61,726 bytes.

CHANGELOG, docs/STATUS.md and webview/README.md describe steps 3 and 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
webview/tools/screenshots/capture.mjs opens documents in the built viewer
in headless Chrome and saves one PNG per state plus index.json (what was
clicked, what the page posted, what it showed, console errors).

- Chrome is driven over --remote-debugging-pipe (fds 3 and 4, JSON
  messages separated by NUL), so it runs on Node 20 without WebSocket.
  Chrome is found through CHROME or the usual macOS, Linux and Windows
  install paths; otherwise it exits with a message saying how to set it.
- The page plays the VS Code side: a small table of Dark Modern, Light
  Modern and Dark High Contrast colours (VS Code 1.139 theme files, MIT),
  an excerpt of VS Code's default webview stylesheet, a stub
  acquireVsCodeApi, and the panel's own inline bootstrap read from
  vscode-extension/src/authoredPanel.ts. After `ready` it gets the frames
  the extension posts: init, workflow, and for the stale states the step 1
  `stale` frame and the stale banner.
- States: initial, select-node, hover-node, hover-connection, focus-mode,
  filter, search, finding (with its suggestion), stale, stale-selected and
  narrow-selected (900x800).
- Inputs: samples/configured_training.mlview.json (read only) and a
  synthetic 120-step document built from tools/benchmark-model.mjs, or
  --artifact/--workspace for local artifacts (cited files are hashed
  against the published hashes). --viewer loads another checkout's dist
  and bootstrap for before/after pictures.

Not part of npm test, CI or the e2e gates; no artifacts or screenshots are
committed. test/screenshot-pipe.test.mjs checks the pipe framing, the
client and the Chrome lookup without Chrome. Docs: webview/README.md,
scripts/README.md, CHANGELOG.md, docs/STATUS.md, THIRD_PARTY_NOTICES.md
(and its extension copy). The pictures are a rendering check with a
simulated host, not live VS Code validation or usability evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int wording

Fixes from independent model reviews of the M1 branch. Each code fix has
a regression test that fails without it. Local tests only (jsdom, the mock
vscode module, a child Node process); not tried in live VS Code. Geometry
golden byte-identical; no contract change, no new setting.

- M1-R1, M1-R3, UX-1: the first click of a double-click arms an opener
  (webview/src/ui/doubleclick.ts); a root capture listener takes the second
  click of the same sequence (MouseEvent.detail >= 2), wherever it lands, and
  opens what the first click selected, swallowing the rest of the gesture.
  A double-click on a finding or Outline step now opens (the first click
  rebuilt the rows, so the dblclick went to a detached node); a shifted row,
  a rail that opens over the canvas, its scrim, the canvas background or an
  Inspector control under the second click no longer break it. The row
  panels' dblclick delegation is gone. Tests send Chrome's click, click,
  dblclick order.
- M1-R2: each source jump has a sequence number; an overtaken notebook jump
  no longer clears or moves a later jump's highlight.
- A11Y-1: the range highlight also draws editor.rangeHighlightBorder, which
  High Contrast themes define instead of a background.
- COPY-1: in the root-hint case the host sends reason 'elsewhere'; marks,
  status bar, Inspector, chips and jump toasts say "in another folder" and
  point to the notice. The host's own jump refusal agrees.
- COPY-2: the Inspector stale note no longer says the claim was not
  re-checked.
- COPY-3: the root-hint text leads with the files and names each folder once.
- A11Y-2: stale marks use --mlv-stale-ink (warning mixed 30% toward text):
  4.2:1 or more in Light Modern and 2026 Light (was 2.2-3.0:1);
  test/stale-contrast.test.mjs checks six default themes.
- M1-R4: the spoken claim no longer stops at "i.e."/"e.g."/"etc.".
- DOC-1: the shortcut sheet and docs say which targets a double-click opens.
- PRUNE-1: findChrome joins macOS/Linux candidates with path.posix; a test
  runs it with node:path mapped to path.win32.
- PRUNE-2: the docs say the screenshot capture is opt-in but its pipe test
  runs in npm test, CI and both e2e drivers.

Rebuilt webview/dist and vscode-extension/media.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…extension host

In a single-folder window the root hint's "Add folder to workspace" makes
VS Code enter an untitled multi-root workspace: it stops the extension host,
switches the workspace and starts a new host. The open panel kept its tab
but nothing answered it. VS Code 1.139 calls a panel serializer only for the
lazily restored tabs it deserializes when a window loads, never for a live
panel after a host restart, so the serializer was never reached.

Before adding the folder in a single-folder window, the host now saves a
note in globalState (the new workspace starts with an empty workspace
state): the window's vscode.env.sessionId, the folder, and each open
diagram's artifact and tab title. At activation the new host closes one dead
diagram tab per noted panel (window.tabGroups, TabInputWebview viewType
"mainThreadWebview-mlview.authoredDiagram", front tabs first) and opens the
diagram again in the same group, where it validates against the added
folder. The note is used once and ignored when older than a minute, from
another window, or for a folder not in the workspace. A host that sees the
folder change itself withdraws its own note. Multi-root windows keep
re-validating in place. Restore now keeps one panel per artifact.

Tests: test/folder-add-restart.test.js (11 cases); the mock gains
tabGroups, TabInputWebview, workspaceFile, env.sessionId and a memento.
Checked live in an isolated VS Code 1.139 Extension Development Host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t restart

Commit 48d5957 saved a reopen note only when the root hint's own button
added a folder, so every other extension host restart in a window still left
a dead diagram tab: VS Code's "Add Folder to Workspace...", "Save Workspace
As...", "Developer: Restart Extension Host", an extension install or update
that restarts extensions. The root hint's notification also outlived the
restart with dead buttons.

Each host now keeps a registry of its open panels in globalState
("mlview.openPanels": per vscode.env.sessionId, each panel's artifact, tab
title and column), written on open, retitle, group change and close but not
at shutdown. At activation, when this session has entries, the new host
replaces each dead tab with a panel for its artifact in the same group. A
dead tab and a restored tab not yet shown look the same to the tabs API, and
a restored tab is revived by the serializer as soon as it comes to the
front, so only front tabs are judged, 500 ms after activation or a tab
change; a front diagram tab that no panel shows by then is dead. VS Code
revives a restored panel with column 0 and reports its group about 20 ms
later, so a visible panel with the tab's title counts until then. A dead tab
behind others is replaced when it is brought to the front. An artifact
already open gets no second panel. Other sessions' entries are pruned after
14 days (at most 20 kept). The hint's Add folder only flushes the registry.
The root hint no longer shows a notification; the panel notice carries both
actions.

Tests: test/host-restart.test.js (17 cases) replaces folder-add-restart.test.js,
whose decoy test passed for the wrong reason. The mock gains Disposable, tab
events, TabGroup.activeTab, panel-linked tabs and view states. A mutation
check of the new logic caught 19 of 19 mutations. Extension tests: 299.
Checked live in isolated VS Code 1.139 Extension Development Hosts; an
extension install or update was not tried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fter a restart

An independent live check passed the generalized recovery and found three
guards no test pinned: the settle delay restarting at each tab change,
title matching even when an entry has the tab's group, and a column-0
panel covering only a tab with its own title. Three tests now pin them
(each fails with its guard removed). The docs now say a diagram put back
after an extension restart starts with a fresh view, while Reload Window
keeps the selection and zoom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realmyang
realmyang merged commit a914f66 into main Oct 2, 2026
@realmyang
realmyang deleted the viewer-m1-loop branch October 30, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant