# Inkhaven 1.2.13 — in flight

1.2.12 was the "fullscreen split view" release —
four-phase rework that gave two paragraphs full
editor chrome side by side, with universal
Shift+Enter pin, sibling-book lookup for
translation, F12 critique-compare across panes,
and the snapshot-as-secondary draft-vs-current
workflow.  Plus the concordance export CLI,
HJSON-editor LLM review, per-detector underline
overrides, and doc-extractor polish.

This document is a living scratchpad — entries land
here as the work lands.  Each item starts as a one-
line plan; gets fleshed out when the corresponding
feature ships; gets a chord / config / hook callout
when relevant.

## Planned themes

### Carryovers from 1.2.12

  * **Mouse-driven scene-break selection** —
    deferred from 1.2.11 → 1.2.12; still on the
    list.  Drag from one scene-break line to the
    next selects the scene.  Pairs with the
    existing `Ctrl+B <` / `Ctrl+B >` scene-break
    navigation.

  * **Inline streaming-response pane inside the
    `Ctrl+B 0` HJSON editor.**  The 1.2.12 LLM
    review (`Ctrl+R`) routes the response into
    `App.inference` — visible in the AI pane only
    after closing the modal.  Carving the bottom
    1/3 of the HJSON-editor modal for an inline
    response pane would let the user critique +
    fix without context-switching.

  * **Full per-overlay parity for the split-view
    secondary editor.**  Phase C deferred:
    search highlights, style-warning detectors,
    lexicon highlights, grammar overlays, tag
    overlays all run on the primary's
    `draw_editor` but not on
    `draw_secondary_editor`.  In practice OK for
    translation / reference workflows (user reads
    the secondary, edits the primary) but a
    future commit could thread the overlay
    passes through.

### Other candidates

  * **Sixth language for the supported set** —
    Italian / Portuguese / Japanese.  Substantial
    content lift: stop-words + Snowball stemmer
    plumbing (where available) + SDT vocabulary
    lists + embedded prompt set in the new
    language.  Bootstrap CLI already exists.

  * **More "diagnose → AI fix" pairs.**  The
    1.2.11 rhythm-rewrite chord (Ctrl+B Shift+M)
    pairs with the rhythm gauge (Ctrl+B Shift+H);
    same shape could apply to repeated-phrase
    detector → AI rewrite, weak-verb detector →
    AI replace, etc.

  * **Live theme preview in config TUI.**  The
    HSL slider already shipped (1.2.11) but its
    preview is widget-local.  Applying the
    candidate colour to the live editor pane
    would catch contrast issues without saving +
    restarting.

  * **GitHub Release object follow-up.**  Both
    1.2.11 and 1.2.12 tags pushed but the
    Release UI object wasn't created (gh CLI
    auth lacked `workflow` scope at release
    time).  Backfill both, then either keep
    refreshing the scope before each release or
    automate via `.github/workflows/release.yml`.

## Implementation log

### Language book — Phase A (foundation)

Lays the groundwork for the invented-language
tooling described in
`Documentation/PROPOSALS/LANGUAGE_BOOK.md`.  Phase
A is the scaffolder only — no lexicon overlay (B),
no AI translation flow (C), no export / doctor /
tests (D).

* **`SYSTEM_TAG_LANGUAGES = "language"`** added to
  `src/store/mod.rs`.  Top-level `Language` system
  book auto-creates on next project open (or
  upgrade from a pre-1.2.13 project) alongside the
  existing seven system books.  The new book lands
  at canonical order index 6 — after Artefacts,
  before Typst.  Existing user books shift up by 1
  if they collided with the slot; the heal-pass at
  the end of `ensure_system_books` mops up.

* **`inkhaven language init <name>`** CLI
  subcommand.  Scaffolds a per-language sub-book
  under `Language` with the five standard
  chapters (`Meta`, `Dictionary`, `Grammar`,
  `Phonology`, `Sample texts`) and seeds
  `Meta/overview` with a starter HJSON
  configuration — alphabet defaults to A-Z; the
  author edits before adding dictionary entries
  since the alphabet drives subchapter
  auto-creation in Phase B.  Rejects duplicate
  names with a friendly error (rather than
  silently appending `-2` to the slug).

  Worked example:

  ```
  $ inkhaven language init Quenya
  created language book `Quenya` at language/quenya
    · Meta
    · Dictionary
    · Grammar
    · Phonology
    · Sample texts

  Next steps:
    · edit `Language/Quenya/Meta/overview` to set the alphabet + metadata
    · add dictionary entries under `Language/Quenya/Dictionary`
    · add grammar rules under `Language/Quenya/Grammar`
  ```

* **Phase A tests** —
  `standard_chapters_match_proposal` locks the
  chapter shape against the proposal §1 hierarchy
  diagram (adding or renaming a chapter requires
  updating both the constant + the proposal);
  `meta_overview_body_contains_alphabet_field`
  locks the load-bearing `alphabet` metadata key
  so a future seed-body edit can't silently drop
  it.

514 tests pass (was 512 — +2).  Zero new
dependencies.  Phase B (lexicon overlay + card
renderers + `add-word` CLI), Phase C (AI
translation flow + Ctrl+B Q chord), and Phase D
(export / doctor / phonotactic generator) are
queued.

### Language book — Phase B (lexicon overlay + add-word)

The first user-visible payoff of the Language book.
Invented-language words from `Dictionary` chapters
now light up in the manuscript prose, and a CLI
chord adds entries without leaving the terminal.

* **`LexCategory::Language`** added to
  `src/tui/lexicon.rs` next to Place / Character /
  Note / Artefact.  Ranks just above Note in the
  per-cell style-dispatch priority — the dim Note
  underline doesn't override the language overlay.

* **`build_lexicon`** in `src/tui/lexicon_build.rs`
  now finds the `Language` system book, walks each
  per-language sub-book's `Dictionary` chapter
  subtree (locating it by exact title match so
  `Meta` / `Grammar` / `Phonology` / `Sample
  texts` content doesn't pollute the lexicon),
  and feeds each dictionary entry's title in as a
  `Language`-category lexicon name.  Snowball
  stemming applies when the project's `language`
  resolves to a known algorithm; otherwise exact-
  form matching covers the conlang case.

* **Highlight rendering** in `src/tui/highlight.rs`
  paints Language hits with `Style::default()
  .fg(theme.language_word_fg) .add_modifier(Modifier
  ::ITALIC)`.  Italic is the typesetting convention
  for foreign-language inclusions in English prose
  — mnemonic + visually distinct from the bold
  place / character chips and the dim note
  underline.

* **`theme.language_word_fg`** new HJSON colour
  knob.  Empty falls back to a soft mauve-teal
  (`#b4a8e1`) picked to stay distinct from places
  (cyan) / characters (amber) / artefacts (peach)
  / SDT (teal).  Phase D extends with per-Language
  sub-book overrides so multi-language projects
  can colour-code in the manuscript.

* **`inkhaven language add-word`** CLI subcommand.
  Resolves the target language by case-insensitive
  title match, finds its `Dictionary` chapter,
  derives the alphabet bucket from the word's
  first character (uppercased — handles Cyrillic,
  Greek out of the box; `Meta/overview.alphabet`
  consultation lands in a Phase B follow-up for
  Hebrew / Arabic / Asian-script edge cases),
  auto-creates the bucket subchapter when missing,
  seeds the entry paragraph with the four core
  HJSON fields (`word`, `type`, `translation`,
  `example`).  Rejects duplicate words under the
  same language with a friendly error rather than
  the silent `-2` slug suffix.

  Worked example:

  ```
  $ inkhaven language add-word Quenya aiya \
      --type interjection --translation hail \
      --example "Aiya Eärendil!"
  created subchapter `A`
  added `aiya` to `Quenya/Dictionary/A` (interjection · hail)
  ```

* **Phase B tests** —
  `alphabet_bucket_uppercases_first_char`,
  `alphabet_bucket_handles_non_latin` (Cyrillic +
  Greek),
  `alphabet_bucket_returns_none_for_whitespace`,
  `seed_dictionary_entry_includes_core_fields`,
  `escape_hjson_handles_quotes_and_backslashes`.

519 tests pass (was 514 — +5).  Zero new
dependencies.

### Language book — Phase B.2 (parser + chip + paradigm + alphabet)

Four follow-up items from the Phase B deferred list,
shipped together because they share a parser.

* **`language_entry` parser** — new crate-root
  `src/language_entry.rs` finds the fenced
  `​```hjson` block in a dictionary-entry body and
  feeds it to `serde_hjson` for a
  `DictionaryEntry { word, pos, translation,
  example, inflection: BTreeMap<String, String> }`
  struct.  Also parses the Language sub-book's
  `Meta/overview` body into a `MetaOverview` struct
  with the author-declared `alphabet` list.
  Permissive on the open fence (`​```hjson` with or
  without trailing space); strict on the close
  (bare `​```` only, so a second `​```typst` block in
  the same body can't accidentally close the
  HJSON).  Malformed HJSON is surfaced as `Err` —
  silently dropping malformed entries would hide
  the kind of typo a Phase D `language doctor`
  exists to find.

* **Inflection-paradigm expansion** —
  `Lexicon::add_extra_forms` appends inflection
  values from each entry's HJSON map as additional
  Language-category names AFTER the build pass,
  then resorts longest-first so the
  longer-phrase-wins invariant survives.
  `build_lexicon` walks each Dictionary subtree's
  paragraphs, parses each body, and feeds the
  paradigm forms through.  Result: an entry
  defining `inflection: { plural: "aiyar",
  emphatic: "aiyala" }` lights up all three forms
  in prose, not just the lemma.  Closes the
  Snowball gap for invented languages where no
  off-the-shelf stemmer applies.

* **Status-bar chip** — when the cursor lands on a
  Language lexicon hit, the editor footer renders
  `[word · POS · translation]` in italic +
  `theme.language_word_fg` to mirror the overlay
  style.  Chip takes priority over the per-
  paragraph goal gauge (the chip is transient,
  cursor-relative info the user explicitly asked
  for by navigating to that word; the gauge comes
  back the moment the cursor moves off).  Partial
  entries (POS missing, translation blank) drop
  the empty fields rather than rendering hollow
  dots that would look like a parse error.

  `App.language_entries: LanguageEntryIndex` is a
  side-product of `build_lexicon`: a HashMap from
  lowercased surface form (lemma + every paradigm
  form) to the parsed `DictionaryEntry`.  Lookup
  is `O(1)` per render frame; rebuilt with the
  lexicon on every save.

* **`Meta/overview.alphabet` consultation in
  `add-word`** — the CLI now consults the
  author's declared alphabet groupings before
  falling back to first-char uppercase.  An
  author writing `alphabet: ["Aleph", "Beth",
  "Gimel"]` for a transliterated Hebrew language
  gets bucket subchapters titled `Aleph` / `Beth`
  / `Gimel` rather than `A` / `B` / `G`.
  Paired-case Latin (`alphabet: ["Aa", "Bb"]`)
  and per-letter compound groups (Polish `Cz`,
  Welsh `Ll`) work the same way.  Fall-back fires
  when the language has no Meta chapter, no
  overview paragraph, no HJSON block, an empty
  alphabet, or the word's first char isn't
  covered by any declared entry.

  HJSON parse errors on `Meta/overview` propagate
  up as `Err` rather than silently fall back, so a
  typo in the alphabet list surfaces immediately
  rather than producing surprise bucket
  subchapters later.

* **Phase B.2 tests** — 12 new:
  parser: `parses_core_fields_from_seeded_body`,
  `parses_inflection_map`,
  `surface_forms_includes_lemma_and_inflections`,
  `surface_forms_filters_empty_inflection_values`,
  `no_block_returns_none`,
  `close_fence_must_be_bare`,
  `malformed_hjson_reports_error`;
  meta:
  `meta_overview_alphabet_buckets_first_char`,
  `meta_overview_alphabet_multichar_buckets`,
  `meta_overview_empty_alphabet_returns_none`;
  lexicon:
  `add_extra_forms_lights_up_paradigm_inflections`,
  `add_extra_forms_preserves_longest_match_invariant`.

531 tests pass (was 519 — +12).  Zero new
dependencies.

**Phase B follow-up remaining** (deferred):

  * **Card renderers** for Dictionary / Grammar /
    Phonology paragraphs viewed in the Language
    book — the §7 / §10 visualisations from the
    proposal.  Significant render work — Phase D
    candidate.

### Language book — Phase C (AI translation flow, MVP)

The headline AI feature: translate the open
paragraph FROM the project's working language INTO
an invented language defined under the `Language`
system book.  Two-letter chord, single LLM
round-trip, streams into the AI pane.

* **`Ctrl+B Q`** in editor scope — the
  `ai.translate_to_invented` action.  Composes a
  prompt envelope from the target language's
  Dictionary / Grammar / Phonology / Sample-texts
  chapters and streams the LLM's response into
  the AI pane via the shared `Inference`
  machinery (same plumbing the show-don't-tell
  scan + rhythm rewrite use).

* **Language resolution** — MVP rule:
  * Zero Language sub-books → status error
    pointing the user at
    `inkhaven language init <name>`.
  * Exactly one → translate directly into it.
  * Two or more → translate into the first by
    canonical order + warn in the status bar
    (`warn: N languages defined; targeting
    \`<name>\` — per-language chord is Phase C.2`).

  The per-language sub-letter chord
  (`Ctrl+B Q Q` for Quenya, `Ctrl+B Q D` for
  Drow) lands in Phase C.2 — needs the picker
  modal plumbing for ambiguity disambiguation.

* **Prompt envelope** composer in `compose_translation_prompt`
  walks the target sub-book's chapters and assembles
  seven sections:

  1. **System prompt** — explains the LLM's role
     as a translator using the author's grammar +
     dictionary as authoritative; asks for
     translation + per-token gloss + applied
     rules + confidence flags.
  2. **Language overview** — the `Meta/overview`
     body (alphabet, family, reading direction).
  3. **Grammar rules** — every paragraph under
     `Grammar` joined with `---` separators.
  4. **Phonology rules** — every paragraph under
     `Phonology` joined the same way.
  5. **Dictionary** — **RAG-filtered**.  Walks
     every entry in `Dictionary` (the §6 parser
     from Phase B.2 extracts `translation`),
     keeps only entries whose translation appears
     in the source text.  Single-word
     translations match via Unicode word
     tokenisation (`UnicodeSegmentation`);
     multi-word translations (phrasal entries
     like `"of the king"`) need substring match
     on the lowercased body.  Falls back to a
     hint message when no entries match — the LLM
     translates by analogy and flags missing
     vocabulary in the response.
  6. **Sample texts** — first 3 paragraphs under
     `Sample texts`, as register / few-shot
     anchors.
  7. **User prompt** — source paragraph title +
     body.

  Each dictionary entry renders compactly:
  ```
  * `aiya` (interjection): hail — _Aiya Eärendil!_ [forms: plural=aiyar, emphatic=aiyala]
  ```

  At literary scale a typical paragraph (1-3K
  source tokens, ~5-15 relevant dictionary
  entries, ~3-6 grammar rules) lands the envelope
  around 4-8 K tokens — well within any modern
  model's window.

* **Response handling** — for the MVP, the LLM
  output streams into the AI pane.  The user
  reads it there and can copy/paste relevant
  bits back into the editor.  Phase C.2 adds:

  * `Ctrl+B Shift+Q` reverse direction
    (invented → working language) for roundtrip
    testing — "does the LLM understand its own
    grammar?".
  * `Ctrl+B G` insert response at cursor (the
    chord already exists for general AI output;
    needs envelope-aware extraction to grab just
    the translation, not the gloss table).
  * Inline gloss-table renderer in the AI pane.
  * Per-language sub-letter disambiguation
    (`Ctrl+B Q Q`) via a picker modal.

* No new tests for Phase C — the prompt composer
  is heavyweight integration glue (needs a real
  project + seeded language sub-book).  Existing
  Phase B.2 unit tests cover the parser, the
  alphabet bucket, and the paradigm expansion;
  the composer's pieces all rely on those.

### Language book — Phase C.2 (picker + reverse + extract)

Three follow-ups from the Phase C deferred list,
delivered as one batch because they share the
chord / direction / extraction plumbing.

* **Language picker on ambiguity** — `Ctrl+B Q`
  on a project with 2+ Language sub-books now
  pops a small centred picker (mirrors the
  `Ctrl+B L` LlmPicker layout) instead of
  silently translating into the first.  Zero
  languages still errors; exactly one still
  fast-paths through without the modal.

  Picker controls:
  * `↑` / `↓` to highlight.
  * `Home` / `End` for the ends.
  * `Enter` to commit.
  * **`Esc` to cancel.**
  * **First-letter shortcut** — typing `q`
    jumps to + commits on Quenya, `d` on Drow,
    etc.  This is the proposal's `Ctrl+B Q Q`
    sub-letter pattern, unbundled into the
    picker rather than maintained as a separate
    sub-chord (avoids the keymap explosion when
    the author has 5 languages defined and the
    sub-letter table needs disambiguation logic
    on top).  The picker rows render the
    first-letter in `[X]` brackets so the
    shortcut is discoverable from the rows
    themselves, not just the footer hint.

  `Modal::TranslationLanguagePicker { entries,
  cursor, direction, source_title, source_body
  }` is the new modal variant.  Stashes the
  source body at open time so an edit / save /
  reload between open and commit doesn't leave
  the inference reading stale buffer state.

* **`Ctrl+B Shift+Q`** — reverse direction.
  Translate the open paragraph FROM an invented
  language back to the working language.  Same
  envelope shape, flipped `from` / `to` labels
  in the system prompt + user prompt.  The
  picker pops with title `Translate FROM ·
  Ctrl+B Shift+Q` so the user can confirm the
  direction at a glance.

  The natural roundtrip workflow:
  ```
  ¶ original (English) → Ctrl+B Q → translation
    → copy into ¶ next   → Ctrl+B Shift+Q
    → back-translation in AI pane
  ```
  When the back-translation drifts from the
  original by more than register, the grammar
  rules or dictionary entries have an
  inconsistency the manuscript will eventually
  trip over.  Closes the proposal's §13
  roundtrip-test concept for interactive use;
  the full `inkhaven language test <name>`
  corpus-driven version is still a Phase D
  candidate.

* **Translation-aware `I` apply** — the AI
  pane's `I` (Insert) chord now lifts ONLY the
  `<<<TRANSLATION>>> … <<<END>>>` block from a
  translation response.  The gloss table +
  applied-rules list + confidence flags stay in
  the AI pane for the author's reference but
  don't pollute the manuscript paragraph that
  receives the insert.

  Implementation:
  * `App.pending_translation: bool` flag set
    when a translation inference is spawned;
    cleared after the first Insert extraction
    so a second `I` press falls back to the
    full body (escape hatch when the LLM forgot
    the markers).
  * `extract_translation_text` mirrors
    `extract_corrected_text` — finds the begin
    marker, lifts everything up to the next
    `<<<END>>>` (the markers share the same
    close token; the LLM is more reliable with
    one sentinel across prompt families).
    Returns `None` on missing or empty content
    so the caller can fall back to pasting the
    full response with a status-bar warning.
  * The translation system prompt instructs the
    LLM to emit `<<<TRANSLATION>>>` /
    `<<<END>>>` around the target-language
    block.  The proposal's `Ctrl+B G` insert-
    response chord doesn't get its own action
    because `Ctrl+B G` is already
    `LookupNotes` — `I` in the AI pane already
    means "insert at cursor", and translation-
    aware extraction makes it Do The Right
    Thing for translation responses too.

* **Phase C.2 tests** — 4 new:
  `translation_extracts_marker_block`,
  `translation_extracts_multiline_prose`,
  `translation_returns_none_when_markers_missing`,
  `translation_returns_none_on_empty_block`.

535 tests pass (was 531 — +4).  Zero new
dependencies.

**Phase C.2 follow-up remaining** (deferred):

  * **Inline gloss-table renderer** — the AI
    pane currently renders the LLM's markdown
    table as-is.  A dedicated renderer that
    aligns the `source · gloss · target`
    columns with column-typed colours would
    make the gloss easier to scan during
    proofreading.
  * **Confidence-flag chip** — when the LLM
    returns "dictionary missing for X", a
    one-key chord to jump straight into
    `inkhaven language add-word`'s seed prompt
    would close the diagnose → fix loop.
  * **Card renderers** for Dictionary / Grammar
    / Phonology paragraphs viewed in the
    Language book — the §7 / §10 visualisations
    from the proposal.  Significant render
    work — Phase D.2 candidate.

### Language book — Phase D (doctor + export)

Two CLI features round out the 1.2.13 Language-
book work: a health report and a three-format
exporter.  Both run headless — no UI integration
— so they slot into authoring scripts +
publishing pipelines without spinning up the
TUI.

* **`inkhaven language doctor <name>`** — health
  report for one language sub-book.  Walks every
  chapter, parses each entry's HJSON (via the
  Phase B.2 parser), and emits a structured
  summary:

  * **Overview** — name, kind, family, ISO code,
    alphabet entry count, reading direction
    (all from `Meta/overview`).  `MISSING or
    unparseable` when the body has no HJSON
    block.
  * **Chapter counts** — parseable dictionary
    entries (with unparseable count flagged
    separately so pre-Phase-B authoring is
    visible), grammar rules, phonology rules,
    sample texts.
  * **Dictionary coverage** — entries with
    examples (% of total), entries with
    inflection paradigms (% of total),
    missing-paradigm count flagged with the hint
    that the lexicon overlay won't catch
    inflected forms for those words.
  * **Manuscript gap analysis** — unique words
    (≥2 characters) in user-book prose, how
    many appear as dictionary translations,
    coverage percentage, and a sample of up to
    15 uncovered words sorted alphabetically.
    Walks the hierarchy filtering out every
    system book (Notes / Places / Characters /
    Artefacts / Prompts / Language / Typst) so
    the gap analysis sees only manuscript
    prose, not author reference material.

  Exit code is always 0 — the report is
  informational, not a pass/fail gate.

* **`inkhaven language export <name> --format
  <fmt> --output <path>`** — three formats land
  in Phase D:

  * **`--format json`** (default) — full
    structured dump.  Top-level keys: `language`,
    `meta`, `dictionary` (sorted by lemma),
    `grammar`, `phonology`, `sample_texts`.
    Pretty-printed for human inspection +
    downstream `jq` ergonomics.  Streams to
    stdout when `--output` is omitted.
  * **`--format anki`** — CSV deck importable
    by Anki / SuperMemo / Mochi.  Columns:
    `word`, `translation`, `type`, `example`,
    `inflection` (semicolon-joined `key=value`
    pairs).  RFC 4180-style quoting kicks in
    when a field contains `,`, `"`, or
    newline.  Streams to stdout when
    `--output` is omitted.
  * **`--format dictionary-twocol`** — A4
    two-column printable Typst dictionary.
    Title centred at the top (`Quenya
    dictionary`), italic subtitle for kind +
    family when authored, then alphabet
    bucket headers (`— A —`, `— B —`, …) with
    each bucket's entries underneath: bold
    headword, italic POS, translation,
    optionally an indented italic example, and
    a small-font `forms — key: value, …`
    paradigm line.  Requires `--output
    <path.typ>` — the Typst renderer doesn't
    stream.

  Entries are sorted by lowercased lemma so
  every format produces a stable diff across
  runs (helpful for version control of
  generated artefacts).

  Author-supplied text passes through a small
  Typst escape (`*`, `_`, `#`, `[`, `]`, `\`
  get backslashed) so dictionary content can't
  trigger Typst's emphasis / code / link
  semantics.  Authors with adversarial input
  (raw Typst inside translations) should use
  the `json` format instead.

* **Phase D tests** — 5 new:
  `csv_field_quotes_when_needed`,
  `typst_escape_handles_markup_chars`,
  `render_anki_emits_header_row`,
  `render_anki_renders_entry_row`,
  `render_dictionary_twocol_groups_by_alphabet`.

540 tests pass (was 535 — +5).  Zero new
dependencies (`serde_json` was already
in-tree).

### Language book — Phase D.1 (CLI completeness + in-TUI scaffold + tutorial)

Six small polish items shipped together to round
out the Language-book feature.

* **Tree-pane Add Book under `Language` auto-
  scaffolds** — closes the discoverability gap
  where new authors had to drop to a shell to
  scaffold a language.  When the Add Book commit
  handler detects the parent is the Language
  system book (via `system_tag ==
  SYSTEM_TAG_LANGUAGES`), it routes through a
  shared `scaffold_language_chapters` helper that
  creates the same 5-chapter shell + seeded
  `Meta/overview` as `inkhaven language init`.
  Status bar confirms with `added language `…`` —
  5 chapters scaffolded; edit Meta/overview to
  set the alphabet`.  The CLI `init` path also
  uses the shared helper, so the two entry points
  produce byte-identical scaffolds.

* **`inkhaven language list`** — one row per
  defined language with dictionary entry count,
  grammar rule count, phonology rule count,
  sample-text count.  Column-aligned to the
  longest language name.  Companion to `doctor`
  for at-a-glance multi-language overview.

  ```
  $ inkhaven language list
    name      words  grammar  phonology  samples
    ------------------------------------------------
    Quenya       42        6          3        4
    Sindarin     17        2          1        2
  ```

* **`inkhaven language remove-word <language>
  <word>`** — mirror of `add-word`.  Resolves the
  language sub-book → Dictionary chapter →
  bucket subchapter (same alphabet derivation
  the add path uses) → entry paragraph; calls
  `Store::delete_subtree` to drop both the
  bdslib node and its on-disk
  `Dictionary/<bucket>/<word>.typ` file.  Errors
  when the entry doesn't exist rather than
  silently no-op-ing so the caller knows their
  `remove-word foo` against an already-removed
  entry needs no follow-up action.

* **`inkhaven language doctor --json`** —
  structured output for CI gates and shell
  pipelines.  Same metrics as the human report,
  shaped as:

  ```json
  {
    "language": "Quenya",
    "meta": { ... },
    "chapters": {
      "dictionary_parseable": 42,
      "dictionary_unparseable": 0,
      "grammar": 6, "phonology": 3, "sample_texts": 4
    },
    "coverage": {
      "with_example": 38,
      "with_example_pct": 90,
      "with_paradigm": 12,
      "with_paradigm_pct": 28,
      "missing_example": 4,
      "missing_paradigm": 30
    },
    "manuscript_gap": {
      "unique_words": 1247,
      "uncovered_count": 1216,
      "coverage_pct": 2,
      "uncovered_sample": [...]
    }
  }
  ```

  Authors gate releases with `inkhaven language
  doctor Quenya --json | jq
  '.coverage.with_example_pct < 80'`.

* **`translate[on]` chip in the AI pane title** —
  italic + `theme.language_word_fg` (mirroring
  the editor overlay's Language style) appears
  in the AI pane title bar while
  `pending_translation` is set.  Author knows
  at a glance that the upcoming `I` apply
  chord will use translation extraction
  (lift only the `<<<TRANSLATION>>>` block),
  not raw insert.  Vanishes after the first
  Insert clears the flag.

* **Tutorial 49 — end-to-end Language book** —
  `Documentation/Tutorials/49-language-book.md`.
  The standard 1-per-release tutorial pattern,
  covering scaffold → populate Meta → add-word
  → see the overlay + chip → `Ctrl+B Q`
  translation → `Ctrl+B Shift+Q` roundtrip →
  `I` insert with marker extraction →
  doctor + export.  Cheat-sheet table at the
  end summarises every chord + command in one
  glance.  README index updated.

No new tests for Phase D.1 (the additions are
integration glue — CLI subcommand wiring, modal-
commit branching, AI pane chip rendering — none
of which add new pure-logic surfaces beyond what
Phase D's existing tests cover).  540 pass.

**Phase D follow-up remaining** (deferred to D.2
or 1.2.14):

  * **`--format grammar`** — printable grammar
    reference: TOC + chapter per rule category
    + examples tables + cross-refs.  Needs
    rule HJSON schema design first (the
    Phase B.2 parser doesn't currently know
    about Grammar rule frontmatter).
  * **`--format phrasebook`** — Sample-text
    chapter rendered as a phrasebook, gloss
    on left, working language on right.
  * **`inkhaven language test <name>`** —
    roundtrip translation test.  Walks a
    corpus of sample texts through `Ctrl+B Q`
    → `Ctrl+B Shift+Q` and reports drift.
    Headless flavour of Phase C.2's
    interactive roundtrip; needs the LLM
    call to run synchronously instead of via
    `Inference`.
  * **`inkhaven language define-rule
    <language> <rule_id> --category <cat>`**
    — open the rule template in `$EDITOR`.
    Pairs with the deferred `--format
    grammar` once the rule schema settles.
  * **`inkhaven language translate <language>
    <text>`** — headless flavour of the
    in-TUI translation chord.  Useful for
    pipelines and shell-pipe-driven
    authoring.
  * **`Ctrl+B Shift+R` in Language book** —
    reverse-lookup picker (find dictionary
    entry by translation).  Pairs with the
    Phase B.2 `LanguageEntryIndex` once an
    inverse-index field lands.
  * **`Ctrl+B Shift+W`** — word-of-the-day
    in the manuscript editor (random
    dictionary entry as a floating card) /
    phonotactic generator in the Language
    book.  Needs the §10 card renderer.
