# Inkhaven 1.2.16 — Author intelligence + polish

1.2.15 was the **stability + self-healing +
security hardening** release — survivable
panics (panic-hook rescue files + crash report
+ `inkhaven recover` CLI), atomic writes
across every user-data save path, 62 panic
sites eliminated from the hot path, background
health monitor with status-bar chip + audit
log, on-demand project doctor with TUI panel
on `Ctrl+B Shift+0`, and a full security
audit that drove four hardening patches
(path-traversal in recover + HJSON config,
`ink.fs.*` sandbox, Bund script trust gate)
plus the new `Documentation/SECURITY_WARNING.md`
risk disclosure.  Tests 565 → 695 (+130).

1.2.16 turns that infrastructure into
author-facing value.  Headline: a unified
**manuscript intelligence dashboard**
(`Ctrl+V Shift+J`) that synthesises every
metric inkhaven has been collecting since 1.2.5
(word count, streak, structure, pacing,
rhythm, POV, thread health, comments digest)
into one pane.  Around it ship the deferred
polish items (language CLI exports from 1.2.13,
snippet picker placeholders + `bund:` prefix
from 1.2.14, DB-side health checks from
1.2.15), a new scriptable surface
(`ink.review.*` / `ink.thread.*`),
narrative-audit detectors (dropped
characters, pacing collapse, stalled
threads), and the first slice of a
worldbuilding-consistency pack (naming
inconsistency + glossary chip).

See `Documentation/PROPOSALS/1.2.16_PLAN.md`
for the original phase shape, sub-phase scope,
test budget, and risk register.

Out of scope this cycle (per agreed direction):
TTS work (Piper transition deferred to 1.2.17),
multi-user / collaboration, Git integration,
HTML export, A.4 local-model maturity, mystery
+ historical-fiction genre packs (deferred to
1.2.17 to keep 1.2.16 worldbuilding-pack-only),
M1 + M3 security hygiene (ride a future docs
commit).

Deferred mid-cycle to 1.2.17:

  * **I.1 — Performance pass.**  The proposal
    scheduled `cargo flamegraph` against a 10K-
    paragraph fixture + targeted fixes (lazy
    tree row materialisation, background
    embedding refresh, DuckDB write batching)
    at the end of 1.2.16.  Pulled out to keep
    1.2.16 release-ready on schedule; the work
    is more valuable when it has its own design
    + benchmark-pinning iteration in 1.2.17.
  * **`ink.thread.add_waypoint` / `link_paragraph`,
    `ink.workflow.chain`** — I.4.b/c follow-ups
    that didn't fit alongside the I.4.a review +
    thread.list landing.
  * **A.2.b dashboard sections** — POV chip,
    rhythm gauge averaged across the project,
    filter-words flagged today, recent AI
    critique line.  Each adds one row to the
    existing `Ctrl+V Shift+J` modal without new
    infrastructure.
  * **A.6.b unresolved-tension detector** —
    heuristic needs real-manuscript calibration
    before shipping.
  * **A.5.b worldbuilding pack additions** —
    numeric / spatial contradictions +
    anachronism overlay.

## Planned themes

### Carryovers from 1.2.15

#### Phase G — Git / backup safety rails (deferred)

The 1.2.15 proposal listed Phase G out of scope
"to keep 1.2.15 scope crisp".  It pairs with the
H/D themes that landed and would compound nicely:

  * **Uncommitted-changes warning at TUI exit** —
    detect a dirty git working tree on exit;
    surface a status-line warning (and an opt-in
    confirm) so users don't leave their changes
    behind.
  * **Backup-freshness chip** — pair with the
    1.2.15 health monitor's backup check.  Show
    a status-bar chip when the newest backup
    crosses an "amber" threshold (50 % of
    `backup.max_age`) so the user has visibility
    BEFORE the monitor's hard warning fires.
  * **Disk-space pre-flight** — before save /
    assemble / backup operations, check free
    space and refuse to start under critical-low
    conditions instead of trapping a write error
    half-way through.

#### M1 — Crash-report action-ring detail hygiene

Audit every `crash::context().push_action(...)`
call site and document the rule that `detail`
must carry structural metadata only (slug names,
counts, anchor IDs), never raw user input.
Truncate `detail` to ~80 chars at insertion as
defence-in-depth.  Documented in
`Documentation/SECURITY_WARNING.md` §3.5 + §4.2.

#### M3 — Script-initiated `ink.input` modal prefix

Prefix modals opened by Bund scripts with a
visible `[script: <slug>]` tag so users can
distinguish them from inkhaven-internal
prompts.  Mitigation for the "phishing prompt"
class flagged in the 1.2.15 security audit.

#### DB-side health checks (deferred from H.2)

The 1.2.15 health monitor shipped three disk-
side checks (project root reachability, backup
freshness, rescue orphans).  Six more were
catalogued in the proposal but deferred:

  * DuckDB `PRAGMA integrity_check`
  * HNSW vector parity vs. DB row count
  * Open paragraph textarea vs. on-disk file
  * Tree parent-pointer integrity
  * Disk-space free %

  (The 1.2.15 list also had "Tantivy index
  integrity" — that was a mistake.  There is no
  Tantivy in the dep tree; the in-tree storage
  layer's only search index is HNSW.  See
  Documentation/MAINTENANCE.md § "Search model"
  for the historical context.  Phase T fixes
  the reference everywhere user-facing.)

These all need cross-thread access to the
project Store (the monitor task lives in its
own tokio task; Store doesn't currently flow
across cleanly).  1.2.16 lands the safe-sharing
story — probably `Arc<RwLock<Store>>` or an
inversion-of-control "TUI asks for checks"
pattern — and ships the remaining checks.

### Carryovers from 1.2.14

#### Snippet expansion — picker placeholders + `bund:` prefix

The Q.2 snippet system shipped with 8 built-in
placeholders that resolve synchronously inside
`expand_placeholders`.  Three placeholders + one
prefix were deferred because they need to pause
the synchronous expansion pipeline for an async
event:

  * **`{char_lookup}` / `{place_lookup}` /
    `{artefact_lookup}`** — open the corresponding
    system-book picker and substitute the chosen
    entry's display name.
  * **`bund:` prefix** — when the snippet body
    starts with `bund:`, the rest is interpreted
    as a Bund-VM program whose output becomes
    the expansion.

  Both unlock advanced authoring flows but
  require the same async snippet state machine.

### Carryovers from 1.2.13

#### Language-book D.2 export formats

  * `inkhaven language export --format grammar`
    — printable grammar reference (TOC + chapter
    per rule category + examples tables).
  * `inkhaven language export --format phrasebook`
    — Sample-texts chapter rendered as a
    phrasebook (gloss left, working language
    right).
  * `inkhaven language export --format csv` —
    round-trip-compatible CSV that the `--import`
    path can re-ingest.
  * `inkhaven language test <name>` — headless
    roundtrip-translation drift CLI for CI gates.
  * `inkhaven language translate <language>
    <text>` — headless flavour of the in-TUI
    `Ctrl+B Q` chord.
  * `inkhaven language define-rule <language>
    <rule_id> --category <cat>` — open the rule
    template in `$EDITOR` for hand-editing.

#### Language-book UX polish

  * `Ctrl+B Shift+R` reverse-lookup picker (find
    dictionary entry by translation).
  * `Ctrl+B Shift+W` word-of-the-day card +
    phonotactic generator.
  * Card renderers for Dictionary / Grammar /
    Phonology paragraphs.
  * Inline gloss-table renderer in the AI pane.
  * Confidence-flag chip (LLM "dictionary
    missing for X" → one-key jump into
    `language add-word`).
  * Inflection-aware status-bar chip.

### Other candidates

  * **Sixth language for the supported set** —
    Italian / Portuguese / Japanese.
  * **More diagnose → AI-fix pairs** — repeated-
    phrase detector → AI rewrite, weak-verb
    detector → AI replace, etc.
  * **Live theme preview in config TUI** — apply
    the candidate colour to the live editor
    while sliding.
  * **GitHub Release object follow-up** — backfill
    Release UI objects for 1.2.11/12/13/14/15
    (tags exist but the Release pages don't yet).

## Implementation log

### Phase T — Tantivy reference correction

Discovered during 1.2.16 scoping that the 1.2.15
proposal listed "Tantivy index integrity" as a
candidate health check.  Inkhaven has no Tantivy
dependency — historically the bdslib path
provided one, but the in-tree `src/storage/`
migration moved search to HNSW-only (via the
`vecstore` crate + fastembed embeddings).

Phase T sweeps every active reference:

* `src/health/mod.rs` — three doc comments
  corrected; `HealthClass::Index` annotated as
  "reserved for a future inverted full-text
  index layer.  No check emits this class today
  — inkhaven's search is HNSW-only."
* `Documentation/MAINTENANCE.md` — new "Search
  model" section explaining the historical
  context.
* `Documentation/SECURITY_WARNING.md` — §4.3
  notes the "search is semantic-only" property.
* `Documentation/Tutorials/04-search-and-discovery.md`
  — rewrote the indexing section to remove
  Tantivy.
* `Documentation/Tutorials/52-health-and-doctor.md`
  — Tantivy removed from the deferred-checks
  list.
* `Documentation/PROMPTS.md` — Tantivy hit refs
  dropped.
* `README.md` — three sites refreshed (Storage
  section, install blurb, Built-with credits).

Historical release notes (`RELEASE_NOTES/1.2.1.md`
and earlier) and `PROPOSALS/1.2.15_PLAN.md` are
left intact per the "do not modify historical
release notes" policy — the inaccuracy lived
there at the time of writing, and the
correction belongs in the present tense, not
revisionist edits to the historical record.

### Phase P.5 — Language CLI exports

Closed three of the six language-book carryovers
from 1.2.13.  Three new `LanguageExportFormat`
variants in `src/cli/mod.rs`:

* `csv` — round-trip-compatible CSV (5 columns
  matching `DictionaryEntry`: word, pos,
  translation, example, inflection).  The
  existing `--import` path accepts the same
  shape, so a project can `export --format
  csv > a.csv && import a.csv` without drift.
* `grammar` — typst-rendered grammar reference
  (TOC + chapter-per-rule-category + per-rule
  examples table).
* `phrasebook` — Sample-texts chapter rendered
  as a two-column typst phrasebook (gloss left,
  working language right).

Plus `inkhaven language define-rule <language>
<rule_id> --category grammar|phonology` — opens
the rule template in `$EDITOR`; on save the
edited body is written back to the rule
paragraph (atomic via `crate::io_atomic`).

Six helpers (`render_csv`, `render_grammar`,
`render_phrasebook`, `define_rule`,
`join_inflection`, `extract_hjson_string_field`,
`extract_hjson_examples`, `rule_template`,
`open_in_editor`).  9 unit tests covering shape
+ escaping.

### Phase P.6 — Snippet picker placeholders + `bund:` prefix

The 1.2.14 Q.2 snippet system deferred three
placeholders + one prefix because they needed
to pause the synchronous expansion pipeline for
an async event.  P.6 lands the state machine.

`src/tui/snippets.rs`:

* `SnippetPickerKind` enum — `Char` / `Place` /
  `Artefact`.
* `ExpansionPlan` enum — `Literal(String)` for
  the existing path, `Picker { head, kind,
  tail }` for the new one.
* `analyse(body) -> ExpansionPlan` — handles
  `bund:` prefix via a `\x00bund:` sentinel
  (so the body's `{author}` etc. can resolve
  via the normal sync pipeline before Bund
  evaluation), detects picker placeholders.
* `split_at_picker_placeholder` — splits at the
  FIRST picker placeholder; subsequent picker
  placeholders pass through verbatim in the
  tail (documented limitation).
* 9 unit tests.

`src/tui/app.rs`:

* `Modal::SnippetPicker { kind, tail }`.
* `maybe_expand_snippet` refactored to call
  `analyse()`; on `Literal` it pastes
  immediately; on `Picker` it pastes the head
  + opens the modal.
* `bund:` programs flow through
  `crate::scripting::eval()` with the trust
  gate + category policy applied (a
  `store_write` body in an untrusted project is
  denied).
* `paste_expansion_with_cursor_marker` helper
  extracted.
* `open_snippet_picker(kind, tail)` +
  `handle_snippet_picker_key`.
* `draw_snippet_picker_modal` render.

### Phase P.4-pre — Store sharing invariant

The proposal scheduled an `Arc<RwLock<Store>>`
refactor before P.4's DB-side health checks
could see the project store from the tokio
monitor task.  Investigation showed `Store`
already derives `Clone` (`DocumentStorage` is
`Arc`-backed; every public method takes
`&self`) — the refactor was unnecessary.

P.4-pre is a small commit:

* Compile-time `assert_send_sync<Store>()` in
  `src/health/mod.rs` so a future refactor that
  introduces non-`Send`/`Sync` state into
  `Store` fails the build, not the runtime.
* `MonitorSetup.store: Option<crate::store::Store>`
  field added (marked `#[allow(dead_code)]`
  until P.4 consumes it).
* `src/tui/app.rs` passes `Some(store.clone())`
  in `MonitorSetup`.

### Phase P.4 — DB-side health checks

Three checks now run from the tokio monitor
task, against the cloned `Store`:

* **`check_db_integrity`** — runs `PRAGMA
  integrity_check` every 15 min.  Critical
  severity on a non-`ok` result.  Delegated
  through `StorageEngine::integrity_check` →
  `JsonStorage::integrity_check` →
  `BlobStorage::integrity_check` →
  `DocumentStorage::integrity_check` →
  `Store::integrity_check`.
* **`check_vector_parity`** — every 15 min,
  compares HNSW vector count to DB row count.
  `VECTOR_PARITY_TOLERANCE: usize = 4` (small
  drift expected during background indexing).
  Warn beyond tolerance.
* **`check_tree_integrity`** — every 30 sec,
  walks the project tree for paragraphs with
  dangling `parent_id` references.  Warn on
  any dangling pointer.

Pure classifiers (`classify_db_status`,
`classify_vector_parity`) extracted so the test
suite can exercise the branching logic without
spinning up a `Store`.

`VectorEngine::count` (proxies to
`VecStore::count`) and `Store::row_count` /
`vector_count` rounded out the API surface.

8 unit tests; classify functions are pure +
fully covered.

### Phase P.1 — Amber backup chip

The 1.2.15 backup-freshness check went
straight from `Ok` → `Warn(f)` at `max_age`.
P.1 inserts an `Info`-severity amber tier
between them.

* `ChipState::Info` variant added with the `ℹ`
  glyph + Magenta-on-White colour scheme.
* `HealthEvent::chip()` routes
  `Warning(f)` with `Severity::Info` to
  `ChipState::Info`.
* `BackupConfig.amber_threshold: f32` (default
  `0.5`) — fraction of `max_age` at which the
  amber chip appears.
* `MonitorSetup.backup_amber_threshold: f32`.
* Pure `classify_backup_age(age, max_age,
  amber_threshold)` classifier with three
  tiers (`Ok` / `Info-amber` / `Warn`).
* 6 unit tests.

Disk-space pre-flight from the proposal is
explicitly deferred — same `fs2` / `sysinfo`
dependency blocker P.4 had to work around;
neither was worth pulling in this cycle.

### Phase I.4.a — Bund stdlib expansion

Four new Bund words to bring the inline-
comments + threads surfaces into scripting
range.

* `src/scripting/stdlib/review.rs` (new):
  * `ink.review.list` (`store_read`) — list
    all open + recently-resolved comments.
  * `ink.review.add_comment` (`store_write`) —
    `( para_uuid char_start char_end body --
    comment_uuid )`, atomic write via
    `io_atomic`.
  * `ink.review.resolve` (`store_write`) —
    `( comment_uuid -- bool )`.
* `src/scripting/stdlib/ink.rs`:
  * `ink.thread.list` (`store_read`) — list
    threads with active / dormant flag.
* `src/scripting/policy.rs` `WORD_CATEGORIES`
  updated + 4 contract tests.

Trust gate + category policy apply; default-
denied for the two `store_write` words.

### Phase A.2 — Manuscript intelligence dashboard (headline)

The 1.2.16 headline.  `src/tui/journal.rs`
(new):

* `JournalSnapshot` with `WordCountSection`,
  `StructureSection`, `ThreadsSection`,
  `CommentsSection`.
* `DORMANT_DAYS = 30` (shared with A.6's
  stalled-thread detector + the 1.2.14
  thread doctor — single source of truth).
* `compute_snapshot(progress, hierarchy,
  project_root, goal, target_date)` —
  produces the snapshot from the existing
  TUI surfaces (no new on-disk state).
* `pacing_stats(counts) -> (mean, stdev, cv,
  verdict)` with three bands (`cv < 0.20`
  "steady" / `0.20 ≤ cv < 0.50` "varied" /
  `cv ≥ 0.50` "choppy").
* `to_markdown(snapshot)` — export
  serialization.

`Modal::Journal { snapshot, scroll,
last_status }`, `Action::OpenJournal`
rebindable as `view.journal`, chord
`Ctrl+V Shift+J` ("J for Journal").

`App::open_journal()` +
`App::handle_journal_key()`: `↑↓` scroll one
line, `PgUp/PgDn` 10-row jump, `Home` top,
`e` exports atomic to `<project>/journal-
<UTC>.md`, `Esc` close.

`draw_journal_modal` render.  8 unit tests.

Deferred to A.2.b (1.2.17): POV chip, rhythm
gauge averaged across the project, filter-
words flagged today, recent AI critique line.
Each adds one row to the existing modal; no
new infrastructure needed.

### Phase A.6 — Plot mining

Three narrative-audit detector classes added
to `inkhaven doctor --scan`.  All `Info`
severity, all `apply_fix` returns
"no autofix — author-judgment finding".

* **`dropped-character`** — character mentioned
  in first 30 % of chapters, absent from last
  30 %.  Min 5 chapters (skips short projects).
  Constants `DROPPED_CHARACTER_INTRO_FRACTION
  = 0.30` and `DROPPED_CHARACTER_OUTRO_FRACTION
  = 0.30`.
* **`pacing-collapse`** — chapter > 3× or
  < 0.3× the trailing 5-chapter mean.
  Constants `PACING_HIGH_RATIO = 3.0`,
  `PACING_LOW_RATIO = 0.30`,
  `PACING_TRAILING_WINDOW = 5`.
* **`stalled-thread`** — thread with newest
  waypoint > 30 days old, or empty.  Shares
  `DORMANT_DAYS` with the dashboard + thread
  doctor.

Helpers: `collect_user_book_chapter_ordinals`,
`read_chapter_prose`.  7 unit tests.

Deferred to A.6.b (1.2.17): unresolved-tension
detector — heuristic needs real-manuscript
calibration before false-positive rate is
predictable.

### Phase A.5 — Worldbuilding pack (first slice)

* **`naming-inconsistency`** scan class — walks
  Characters/Places/Artefacts for multi-word
  canonical names (single-word names like
  "Aerin" skipped — too many natural variants);
  scans prose for near-misses (shared first
  word + Levenshtein on remainder, ratio ≤
  50 % of max length).  Standalone
  `levenshtein(a, b) -> usize` impl (no new
  deps).  Pure `classify_naming_inconsistencies`
  classifier extracted for testing.  11 unit
  tests.
* **Worldbuilding glossary chip** — new
  status-bar chip showing `<N>C·<N>P·<N>A`.
  Free fn `glossary_counts(hierarchy) ->
  (usize, usize, usize)`.  `App::glossary_chip_
  spans()` renders to DarkGray bg + White fg.
  Status bar wires it after `pov_chip_spans`.
  Toggle via `editor.show_glossary_chip: bool`
  (default `true`).

Deferred to A.5.b (1.2.17): numeric / spatial
contradictions, anachronism overlay.  Both
need number-words extraction + preposition
parsing + editor render infrastructure that
doesn't yet exist.

### Closing

Eight implementation phases shipped, three
follow-up groups (A.2.b / A.6.b / A.5.b) +
I.1 + I.4.b/c deferred to 1.2.17.  Zero new
dependencies across the cycle.  The carryover
backlog from 1.2.13 / 1.2.14 / 1.2.15 closed
down to language UX polish, mystery + historical
genre packs, and the disk-space pre-flight —
all tracked in `Documentation/RELEASE_NOTES/
1.2.17.md`.

Tests grew across every phase.  No regressions
introduced; the post-1.2.15 stability
invariants (atomic writes, path safety, lock-
poisoning recovery, panic-survivability) held
across every new write site.

Headline takeaway: the dashboard +
narrative-audit detectors turn 1.2.15's
self-healing infrastructure into author-facing
value.  The doctor tells you what's broken;
the dashboard tells you what moved; the new
detectors connect manuscript state to
narrative intent.
