# v1.2.5 — Typst goes in-process

The headline of 1.2.5 is one HJSON line:

```hjson
typst_compile: { engine: "inprocess" }
```

Flip it and inkhaven stops shelling out to the host's `typst`
binary for builds. The full compiler — `typst` + `typst-pdf` +
`typst-kit` (fonts + `@preview` packages) — is linked into
every 1.2.5 binary and runs inside the inkhaven process.
Structured `SourceDiagnostic`s replace stderr blobs. The
external CLI stays the default; the switch is a runtime
decision.

Around that one switch:

1. **Diagnostics** — `typst-syntax` parse check on save / idle
   (Phase 1, always on). Optional full-compile semantic check
   when `engine = "inprocess"`. `Ctrl+V N` jumps the cursor
   between them.
2. **Render preview** — `Ctrl+V R` rasterises the open
   paragraph and floats it on top of the editor. `← / →`
   navigate pages, `S` saves the current page, `A` saves all
   pages.
3. **TUI integration** — splash + `Esc`-cancel + autosave
   before `Ctrl+B A / B / O`. The new engine is a peer of the
   external CLI in every flow.
4. **`inkhaven doctor`** — health report: binary, engine,
   fonts, package cache, project shape, actionable warnings.
5. **Logo in credits** — `Ctrl+B V` now banners the embedded
   `logo.png` above the version + dependency list.

## Headline changes

### In-process compile engine (`engine = "inprocess"`)

```hjson
typst_compile: {
  engine: "inprocess"
}
```

Replaces the `Child::spawn("typst", ...)` path with
`typst::compile::<PagedDocument>` + `typst_pdf::pdf`. The
compile runs on a worker thread; the foreground TUI keeps
animating the spinner. Both engines write the PDF to the same
path.

* **Fonts** — bundled `embed-fonts` set (Computer Modern +
  Linux Libertine, ~10 MB) plus the user's system fonts via
  `typst-kit`'s fontdb searcher. Either source toggleable via
  `bundle_fonts` / `use_system_fonts`.
* **`@preview/<pkg>` resolution** — first fetch is online,
  subsequent uses hit the OS cache directory
  (`~/Library/Caches/typst/packages` on macOS, etc.). Disable
  with `packages_enabled: false` for hermetic builds.
* **Diagnostics** — structured `SourceDiagnostic`s with span
  info, formatted into stderr-shaped strings so the existing
  AI error-analysis hook works unchanged.

### Typst-syntax parse diagnostics (Phase 1)

```hjson
typst_compile: {
  diagnostics:              true   // default
  diagnostics_idle_seconds: 2
}
```

Pulls in `typst-syntax` (parser only — no eval, layout,
render, fonts, package resolver). On every save and on every
`diagnostics_idle_seconds` of editor idle, re-parses the open
paragraph. First parse error lands on the status bar:

```
typst: line 12:5 — expected `}`, found end of file
```

Engine-independent — works the same in `external` and
`inprocess`. Bund and HJSON content types are skipped.

### Semantic diagnostics (opt-in)

```hjson
typst_compile: {
  semantic_diagnostics: true   // needs engine = "inprocess"
}
```

When both flags are on and the parse check passes, a full
`typst::compile` runs against the open paragraph in isolation
and surfaces semantic errors (`#unknown_func` → "unknown
variable", type mismatches, missing-font references). Costs
~20–200 ms per check on warm caches.

**False positives are expected** for paragraphs that reference
book-level definitions. Leave off when manuscripts lean on
preamble-defined helpers.

### Ctrl+V N — diagnostic navigation

Jump the editor cursor to the next typst diagnostic in the
open buffer. Wraps at the end. Status bar reports
`diag 2/5 line 12:5 — <message>`. Pairs with the parse +
semantic checks above for a tight type-save-fix loop.

### Ctrl+V R — render paragraph preview

```
Ctrl+V R                         → save buffer → compile in-process → float PNG preview
  Inside the preview:
    ← / → (or ↑ / ↓)             → previous / next page
    Home / End                   → first / last page
    S                            → save current page at 4.0 px/pt (~288 dpi) via picker
    A                            → save all pages → `<base>-page-001.png`, `-002.png`, …
    Esc                          → close back to editor
```

Cancelling the save picker (Esc) restores the preview with
navigation state intact. Successful writes close back to the
editor.

Requires a terminal with graphics support (kitty / iterm2 /
sixel via ratatui-image). Terminals without graphics surface a
status-bar hint instead of opening an empty modal.

### TUI integration

- **Splash + Esc-cancel** — Ctrl+B B / Ctrl+B O paint a
  centered splash with spinner + book + active engine +
  elapsed seconds. **Esc** cancels: external sends SIGTERM,
  in-process flips a cancel flag (worker thread keeps running
  until typst finishes naturally; foreground unblocks
  immediately). AI error-analysis path is skipped on cancel.
- **Autosave before A / B / O** — assemble, build, take all
  save the primary editor (and the secondary editor in
  similar-paragraph mode) before walking `.typ` files. No
  more "I just pressed Ctrl+B B and the build used yesterday's
  saved version".
- **Engine visibility** — three surfaces:
  * `inkhaven doctor` reports it explicitly.
  * `Ctrl+B V` credits pane carries a `Typst engine` row.
  * The compile splash shows it above `Elapsed:`.

### `inkhaven doctor`

```sh
inkhaven doctor
```

Three sections + warnings: **Binary** (version, repo,
rust-version), **Typst engine** (engine summary, external
typst path, every related HJSON knob), **Package cache**
(path, entries, size), **Project** (hierarchy shape + word
counts when run inside an initialised project), **Notes**
(actionable warnings like `typst is NOT on PATH` or `engine
has BOTH bundle_fonts AND use_system_fonts disabled`).

Pipe-friendly plain-text output — no colour, no TTY tricks.

### Story view — Ctrl+V W

Build a visual graph of the current user book and float it on
top of the editor. Pipeline:

1. Walk the book's hierarchy + count leaves under every node.
2. Lay it out as a **twopi-style radial tree** (book at centre,
   each depth on a concentric ring; sibling wedges sized
   proportional to subtree leaf counts). Lexicon nodes
   (Characters / Places / Artefacts) land on an outer ring at
   the angle of their first mentioned paragraph, with a small
   5°-bucket nudge so co-located names don't stack.
3. Emit SVG by hand — different shape per node kind (book =
   folder, chapter = box, subchapter = octagon, typst
   paragraph = ellipse, hjson = note, script = parallelogram,
   image = chevron; Characters = egg, Places = diamond,
   Artefacts = hexagon). Long titles word-wrap to multiple
   lines (max 22 chars per line × 4 lines, then `…`); each
   node's bounding box scales to fit its wrapped label so text
   never overlaps the boundary. Solid grey edges for the
   structural skeleton, dashed purple for `linked_paragraphs`
   wiki-links, dashed green for lexicon mentions. Edge
   endpoints are truncated by each node's own circumscribing
   radius (which now varies with label size) so arrowheads
   touch the shape, not the centre.
4. `resvg` + `tiny-skia` rasterises the SVG to a PNG. System
   fonts are loaded once into a process-wide
   `usvg::fontdb::Database` (the default empty fontdb made
   labels invisible).
5. ratatui-image protocol displays it in a `Modal::StoryView`
   that floats over the editor.

The radial layout is a hand-rolled twopi implementation
(layout-rs's only mode is Sugiyama hierarchical, which crowded
deep books). Bypassing layout-rs also dropped a ~1k-LOC dep.

Inside the modal:

| Key | Action |
|---|---|
| `S` | Save PNG via picker — default `<book-slug>-story-YYYYDDMM-HHMM.png` |
| `Esc` | Close back to the editor |

The save picker re-uses the same `return_to` stash pattern the
SaveRenderedPng picker uses — Esc on the picker restores the
preview unchanged. No re-render on save; the PNG bytes the
preview displays are exactly what lands on disk.

Lexicon mention detection is case-insensitive substring match
over each paragraph's body — fast on book-sized graphs (a few
hundred ms on a typical book + ~50 lexicon nodes). Only
lexicon nodes that ARE mentioned appear in the graph; the rest
stay hidden so the picture doesn't fill with orphan icons.

### Node tagging system

```hjson
// no config required — tags live in node metadata
```

Three entry points, one project-wide tag namespace:

* **`Ctrl+B ]`** — open the floating tag picker scoped to the
  open editor paragraph. `↑↓` select, `Space` multi-selects,
  `T` applies the selected tags (or the cursor tag if nothing
  selected), `A` prompts for a new tag (auto-selected on add),
  `D` deletes a tag project-wide (y/n confirm shows blast
  radius), `Esc` closes.
* **`Ctrl+B }`** — search-by-tag picker. `Enter` on a tag
  opens a second pane listing every paragraph that carries
  it, with a typeable filter input. `Enter` on a paragraph
  opens it in the editor (autosaves prev; tree cursor
  follows).
* **`g`** in the tree pane — same picker as `Ctrl+B ]` but
  the apply target is the marked set (or the cursor row when
  no marks). `T` applies the selected tag set across every
  marked paragraph in one go.

Tags are stored as `Node.tags: Vec<String>` (serde-default
empty). Free-form strings; case is preserved on save, deduped
case-sensitively in the picker; whitespace trimmed; empty tags
rejected. Project-wide deletion walks every node and rewrites
the metadata blob, reporting `removed from N paragraph(s)` on
the status bar.

### Embedded logo in credits

`include_bytes!("../../logo.png")` ships the project logo
straight in the binary. Decoded lazily into a static
`OnceLock<Option<DynamicImage>>`. `Ctrl+B V` (credits) banners
it above the version + dependency list. Terminals without
graphics support collapse the banner to 0 rows; the rest of
the pane is unchanged.

## Other changes

- **Scrivener importer cleanup** — four dead-code warnings
  from 1.2.4 dropped (`ImportReport` re-export, `BinderItem::walk`
  test-only, `ImportReport::total_created`, `WalkCtx::scriv_root`).
  Zero scrivener warnings on `cargo build`.
- **Save-as picker for Ctrl+V markdown** (carry-over from 1.2.4)
  consolidates with the new `SaveRenderedPng` picker style.
- **`typst_compile::typst_external_path()`** — extracted from
  `cli/export.rs` so the CLI export path and the in-process
  doctor / engine summary share one PATH-walk implementation.

## New HJSON fields (all `#[serde(default)]`)

```hjson
typst_compile: {
  engine:                      "external"   // | "inprocess"
  diagnostics:                 true
  diagnostics_idle_seconds:    2
  semantic_diagnostics:        false
  bundle_fonts:                true
  use_system_fonts:            true
  packages_enabled:            true
  error_system_prompt:         ""          // (1.2.4)
}
```

Existing 1.2.4 configs load unchanged; defaults preserve
1.2.4 behaviour exactly until the user opts in.

## New keybindings

| Chord | Action |
|---|---|
| `Ctrl+V R` | Render paragraph → floating PNG preview |
| `Ctrl+V N` | Next typst diagnostic (jump cursor) |
| `Ctrl+B ]` | Tag picker (open paragraph) |
| `Ctrl+B }` | Search-by-tag picker |
| `g` (tree pane) | Tag picker (marked set or cursor row) |
| `Ctrl+V W` | Story view — DOT graph PNG of current book |
| (in story view) `S` | Save PNG via picker |
| (in render preview) `← / →` | Previous / next page |
| (in render preview) `Home / End` | First / last page |
| (in render preview) `S` | Save current page at full DPI |
| (in render preview) `A` | Save every page |

All view-sub chords (`Ctrl+V ...`) remain rebindable via
HJSON `keys.bindings.view_sub` and `ink.key.bind_view_sub`.

## New CLI

```sh
inkhaven doctor
```

See above. Works inside and outside an inkhaven project.

## Migration notes

- **Default behaviour unchanged.** Every new HJSON field
  defaults to a value that preserves 1.2.4 semantics. Existing
  configs load + run identically.
- **Binary size** grew by the in-process compiler and font
  bundle — debug builds add ~50 MB, release builds less.
- **Clean build time** adds ~60 s on a first build to compile
  the typst umbrella crates. Incremental rebuilds unaffected.
- **First in-process compile** is slower than steady-state
  (Comemo cache cold, font searcher fresh). Subsequent
  compiles in the same session reuse the global font + library
  + package storage singletons.
- **`@preview` first fetch** is online. Air-gapped users
  should pre-populate the package cache or set
  `packages_enabled: false`.

## Files added

- `src/typst_check.rs` — Phase 1 parse diagnostics.
- `src/typst_world.rs` — `InkhavenWorld` impl of `typst::World`.
- `src/typst_inprocess.rs` — in-process compile worker + handle.
- `src/typst_paragraph_render.rs` — paragraph → PNG renderer.
- `src/cli/doctor.rs` — `inkhaven doctor` subcommand.
- `Documentation/Tutorials/24-typst-in-process.md` — workflow walkthrough.
- `Documentation/RELEASE_NOTES/1.2.5.md` — this file.

## Stats

- 189 tests pass + 4 ignored smokes (system-font-dependent).
- 4 new crate dependencies: `typst`, `typst-pdf`, `typst-kit`
  (with `embed-fonts` + `downloads` + `packages` features),
  `typst-render`, `typst-syntax`.
- Approximate diffstat from 1.2.4 tag: +6,000 LOC across src +
  Documentation.
