---
name: hd-editor-state
description: The Editor State Machine (ESM) — HOW to know the true state of the VS Code editor and the Claude Code conversations inside Hydrogen Desktop, and the HARD typing invariants. Use BEFORE typing into a Claude conversation, driving setup-step threads, diagnosing "state unknown", empty split panels, tab spam, prompts landing in the wrong conversation, or any Claude tab/webview confusion. Trigger words — editor state, /editor/state, claude tab state, conversation state, which tab is active, empty editor group, split panel, double-type, typed into two boxes, prompt in wrong conversation, census, safe to type, stray text, disk verified, msg_count wrong, workbench unknown.
---

# hd-editor-state — the Editor State Machine

ONE authoritative read: `GET /editor/state` on HD'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).
