master
Colby Knox Publish 0.3.30 ed6a0ea

name: hydrogen-editor-state description: >- Hydrogen's Editor State Machine: how to know the true state of the VS Code editor and its Claude conversations, and the hard rules before typing into one. Use before typing into a Claude tab, or when a prompt landed in the wrong conversation, a split panel is empty, or the state is unknown.

hydrogen-editor-state — the Editor State Machine

ONE authoritative read: GET /editor/state on Hydrogen's control API. Everything below is what it returns and the rules for acting on it. Full design: docs/EDITOR-STATE-MACHINE.md (hd repo).

The layered truth (never blend layers)

Layer Question Source Trust
L0 lifecycle starting / editor-up / ready port probes (7380, :8821, CDP) absolute
L1 layout+conversations groups, widths, empty panels, tab census, focused tab workbench DOM + tab bar high (VS Code chrome is stable)
L2 extension what commands can I invoke the extension's contributed-command registry + fingerprint absolute (it IS the API)
L3 conversations_disk turns, first prompt, generating, delivered the jsonl transcripts absolute — works for DISPOSED background tabs
L4 census compose boxes + submit buttons visible NOW DOM probes across ALL contexts only under the invariants below

The HARD invariants (John 2026-07-17 — non-negotiable)

  1. Never type unless census.safe_to_type == true (exactly 1 visible input across ALL webview contexts — not just the focused one).
  2. Freshness oracle is the TITLE: title == 'Untitled' = never submitted. NEVER use msg_count for freshness — it counts welcome-screen DOM nodes (reads 5 on a pristine tab).
  3. After a submit, run the stray-text audit: POST /editor/audit-prompt {prefix} must return contexts_containing: 0. 1+ = the prompt is ALSO sitting in another conversation's box (double-type) — clear it, fail loudly, never ignore.
  4. Delivery is proven on DISK, not in the DOM: the prompt must be the first user turn of exactly ONE ~/.claude/projects/*/*.jsonl. 0 = not delivered; 2+ = double-send.
  5. Background tabs are unreadable (VS Code disposes their webviews): loaded:false, null ctx/compose. To read one, activate it first (/claude/tabs/activate {index:<ordinal>} — by ORDINAL, never by label: every Claude tab is labeled 'Claude Code').
  6. normalize_needed: true → POST /editor/normalize before showing the user anything (an empty split group steals 50% of their editor; populated 2-group layouts are preserved).
  7. Prefer registered commands over DOM clicks: extension.commands lists them (claude-vscode.newConversation, .focus, .editor.open, ...). Run via the in-distro :8821/command API. Commands survive extension updates; selectors don't.
  8. Extension drift: extension.fingerprint changes when Claude Code updates — re-verify your assumptions (anchors, commands) before trusting flows after a fingerprint change.

Quick recipes

  • Am I safe to type? GET /editor/state → census.safe_to_type.
  • Did my prompt double-type? POST /editor/audit-prompt {"prefix":"<first words>"} → want 0 after submit.
  • Which conversation is generating? conversations_disk[*].generating (disk truth, any tab).
  • Fix a half-width Claude tab / dead empty panel? POST /editor/normalize.
  • Bars: layout.bars reports activity / primary sidebar / secondary (auxiliary) bar / bottom panel visibility. A Claude conversation living in the SECONDARY bar is a second compose box (census=2). Close bars deterministically: POST /editor/bars {"primary":"close","secondary":"close"} (read -> toggle-on-mismatch -> verify; never a blind toggle).