# Inkhaven 1.2.14 — Plot intelligence + inline comments + quick wins

Status: proposal, 2026-05-31.  Anchors the 1.2.14
development cycle.  See `RELEASE_NOTES/1.2.14.md`
for the living implementation log.

## 1. Themes

### Theme A — Plot / narrative intelligence
A new system book `Threads` that treats each
narrative arc as first-class project content.
Pairs with a thread weave view (analogous to the
timeline swim-lane but indexed on threads instead
of ticks) and an AI thread audit that scores
chapters against the thread set.

### Theme C — Inline comments
Span-anchored comments on paragraph text.
Resolve / unresolve state, multi-author
attribution, optional AI-summarised digest.
Companion to beta-reader workflows.

### Quick wins (alongside the themes)
1. **Project templates** (CLI)
2. **Snippet expansion** (HJSON-controlled)
3. **AI continuation drafting** (`Ctrl+V` chord)
4. **Inline footnote / endnote** (`Ctrl+V` chord)
5. **Project-level word-count goal + projection** (`Ctrl+V` chord)
6. **Style transfer from sample text** (`Ctrl+V` chord)

`Ctrl+B W` typewriter mode already serves the
"distraction-free" use case; not duplicated.

## 2. Chord allocation — `Ctrl+V` prefix

Surveyed `keybind.rs` (`view_sub`): **none of the
chords below collide with an existing binding**.
Plain letters taken: `s g t a i l k b m p h r n w
e` + `Shift+b/p/u/s/W/e/i/t`.  All free:

| New chord | Action name | What it does |
|-|-|-|
| `Ctrl+V Shift+H` | `view.threads_picker` | Open Threads picker — list every thread, filter by status, Enter to open the entry, `Shift+Enter` to pin to split-view secondary |
| `Ctrl+V Shift+A` | `ai.thread_audit` | AI thread audit — chapter / subchapter / book scope (cycles via F9 scope chip) → score each child paragraph for which threads it advances |
| `Ctrl+V c` | `view.add_comment` | Add inline comment on the current selection (or word at cursor) |
| `Ctrl+V Shift+C` | `view.comments_panel` | Open the project-wide comments panel — list unresolved comments, navigate to the source paragraph, resolve / unresolve, optional AI digest |
| `Ctrl+V d` | `ai.continuation_draft` | AI continuation drafting — "continue the open paragraph in my voice" using the previous N paragraphs as voice anchor |
| `Ctrl+V f` | `editor.insert_footnote` | Insert Typst `#footnote[…]` at the cursor; opens a one-line modal for the footnote body |
| `Ctrl+V Shift+G` | `view.project_goal_modal` | Project-level word-count goal + projection: total / goal / pct / days remaining / words-per-day required / projection at current rate |
| `Ctrl+V y` | `ai.style_transfer_rewrite` | Style transfer rewrite — pick a sample paragraph for voice / structure / tone reference, rewrite the open paragraph in that style |

Plain-letter chords reserved for high-frequency
operations; `Shift+` modifiers for modal-style or
project-wide views.  Vim-style `j` / `Shift+J`
navigation INSIDE the comments panel (next /
prev unresolved) is a panel-local key — not a
separate `Ctrl+V` chord.

## 3. Theme A — Threads system book

### 3.1 Data shape

`Threads` joins the seven existing system books
(`Notes`, `Research`, `Prompts`, `Places`,
`Characters`, `Artefacts`, `Language`).  Each
thread is a Paragraph under the Threads book.
Body is pure HJSON (same content-type pattern
as Language entries) so the editor renders with
syntax highlighting and the parser can read the
shape directly.

```hjson
{
  // ──────────────────────────────────────────
  // IDENTITY
  // ──────────────────────────────────────────
  title:         ""    // human-readable name
  // setup | develop | payoff | resolved | abandoned
  status:        "setup"
  // major | subplot | runner | bridge
  weight:        "major"

  // ──────────────────────────────────────────
  // ARC SHAPE
  // ──────────────────────────────────────────
  opening:       ""    // one-sentence opening hook
  midpoint:      ""    // one-sentence midpoint pivot
  payoff:        ""    // one-sentence payoff

  // ──────────────────────────────────────────
  // CONNECTIONS
  // ──────────────────────────────────────────
  characters:    []    // slug refs into Characters book
  places:        []    // slug refs into Places book
  artefacts:     []    // slug refs into Artefacts book
  related_threads: []  // slug refs into Threads book

  // ──────────────────────────────────────────
  // METADATA
  // ──────────────────────────────────────────
  // tension level on a 0-10 scale; rendered as the
  // height of this thread's swim-lane in the weave
  // view.
  tension:       0
  // genre register: literary | thriller | romance | ...
  register:      ""
  notes:         ""
}
```

Paragraphs in the manuscript link to threads via
the existing paragraph-link mechanism (`Ctrl+V A`
add outgoing link, `Ctrl+V I` add incoming) — no
new linking primitive needed.

### 3.2 Tree-pane integration

* `b` chord on the `Threads` system book →
  scaffold a new sub-book.  Same context-
  sensitive routing pattern as 1.2.13's Language
  scaffold.
* `+` chord under a Threads sub-book → create a
  thread paragraph with the HJSON template
  seeded (mirrors 1.2.13's Dictionary entry
  flow).
* `inkhaven thread init <name>` / `add <thread>
  --title <t> --status <s>` / `list` CLI
  surface for headless / scripted authoring.

### 3.3 Thread weave view

`Ctrl+V Shift+H` opens the Threads picker first;
from inside, pressing `w` opens a weave-view
modal.  Layout mirrors the 1.2.6 timeline
swim-lane modal:

```
 ┌─ Thread weave ─────────────────────────────────┐
 │                                                │
 │ inheritance subplot   ●─●───●────────────────● │
 │ secret-society reveal     ●──●────●──●──●──    │
 │ redemption arc        ●────────●─────●──────●  │
 │                                                │
 │ chapter 1   2   3   4   5   6   7   8   9      │
 │                                                │
 │ ─── current cursor ─┘                          │
 └────────────────────────────────────────────────┘
```

Each `●` marks a paragraph that links to that
thread.  Empty stretches surface where threads
go dormant.  `Enter` on a dot opens the source
paragraph; arrows navigate between dots.

### 3.4 AI thread audit

`Ctrl+V Shift+A` from any pane.  Builds a prompt
envelope:

* Every thread's HJSON shape (title, status,
  opening / midpoint / payoff).
* The chapter / subchapter / book under cursor
  (scope picked by the current F9 AiMode).

Asks the LLM to score each child paragraph for
which threads it advances + which it touches
incidentally + which it should advance but
doesn't.  Streams into the AI pane with
`<<<AUDIT>>>` / `<<<END>>>` markers; the AI
pane's `I` apply chord extracts a parseable
report the user can save to disk.

Plus a focused "blind spots" pass that surfaces:
* Threads not advanced in the last N
  chapters (configurable `editor.thread_audit
  .stale_chapter_threshold`).
* Threads marked `payoff` whose payoff paragraph
  hasn't fired yet (no incoming link).
* Threads marked `resolved` that get fresh
  paragraph links (the LLM flags
  "you may be re-opening this").

## 4. Theme C — Inline comments

### 4.1 Storage

One JSON sidecar file per paragraph, alongside
the `.typ` file:

```
books/manuscript/chapter-1/03-rain.typ
books/manuscript/chapter-1/03-rain.comments.json
```

Format:

```json
{
  "schema_version": 1,
  "comments": [
    {
      "id": "01939c2a-...",
      "char_start": 412,
      "char_end": 487,
      "author": "vladimir",
      "created_at": "2026-06-01T10:23:00Z",
      "resolved": false,
      "resolved_at": null,
      "text": "Does this sentence land the redemption beat?",
      "replies": []
    }
  ]
}
```

* **Character offsets** (not byte offsets) so the
  storage survives UTF-8 boundary edits.
* **Sidecar files** (not bdslib) so comments
  travel with the .typ in git and aren't tied
  to the local DuckDB.  Diff cleanly across
  commits.
* **Stable UUID** per comment so a referenced
  comment in an AI digest survives author edits
  to the surrounding text.
* `replies: []` reserved for Phase C.2 threading.

### 4.2 Editor overlay

Commented spans get a per-detector style modifier
(default `underline+italic`) in
`theme.comment_span_modifier`.  The cursor
landing inside a commented span surfaces the
comment text + author + age in the editor footer
(same slot the goal gauge / language chip use).

Resolved comments render with `dim` instead of
underline so the author sees historical context
without active-noise.  HJSON knob to hide
resolved entirely.

### 4.3 Add comment — `Ctrl+V c`

1. With a selection: anchor the comment to the
   selection's character range.
2. Without a selection: anchor to the word at
   the cursor (Unicode word boundaries).
3. Pop a multi-line text input modal for the
   comment body.
4. On commit: write the sidecar JSON + update
   the in-memory comment cache.

### 4.4 Comments panel — `Ctrl+V Shift+C`

Project-wide modal listing every unresolved
comment:

```
 ┌─ Comments — 7 unresolved · 3 resolved hidden ─┐
 │                                                │
 │ → chapter-1/03-rain · l.5 · vladimir 2d ago    │
 │   "Does this sentence land the redemption..."  │
 │                                                │
 │   chapter-3/aerin-pov · l.18 · vladimir 5d ago │
 │   "Foreshadowing for the inheritance?"         │
 │                                                │
 │ ...                                            │
 │                                                │
 │ ↑↓ navigate · Enter open · r resolve · R reopen│
 │ d delete · / filter · a AI digest · Esc close  │
 └────────────────────────────────────────────────┘
```

Panel-local chords:

* `Enter` / `Space` — jump to the comment's
  source paragraph + position cursor at the
  span start.
* `r` — resolve the comment (sidecar JSON
  updated; comment dim-rendered in editor).
* `R` — reopen a resolved comment (toggle the
  filter to show resolved first).
* `d` — delete (confirm in panel).
* `/` — filter (by text, author, paragraph
  slug).
* `a` — AI digest: summarise the unresolved
  comments by theme ("structural" vs "prose"
  vs "factual").  Streams to AI pane.
* `j` / `Shift+J` — vim-style next / prev
  unresolved (skip resolved).
* `s` — sort cycle: by date (default), by
  paragraph slug, by author.

### 4.5 CLI surface

```
inkhaven comments list [--paragraph <slug>] [--unresolved-only]
inkhaven comments add <paragraph> --text <body> [--start <n> --end <n>]
inkhaven comments resolve <comment-id>
inkhaven comments delete <comment-id>
inkhaven comments export --format json [--output <path>]
```

Powers CI / external review tooling (e.g. a
beta-reader bot dropping comments via the CLI;
a digest emailer running on a cron).

## 5. Quick win — project templates

### 5.1 CLI

```
$ inkhaven init my-project --template novel
$ inkhaven init my-project --template nonfiction
$ inkhaven init my-project --template rpg-sourcebook
$ inkhaven init my-project --template technical
$ inkhaven init my-project --template nanowrimo
$ inkhaven init my-project --template empty  # current behavior
```

`empty` is the default (matches today's `init`).
Other templates extend the scaffold with:

| Template | Adds |
|-|-|
| `novel` | Three-act Book (Act I / II / III chapters with placeholder paragraph titles); seeded Characters book with `protagonist` / `antagonist` / `confidant` template entries; `project.word_count_goal: 80000` in HJSON |
| `nonfiction` | Book with Introduction / Part I / Part II / Conclusion chapters; Research book pre-populated with a `methodology` paragraph |
| `rpg-sourcebook` | Book with Setting / Rules / Adventures / Appendices chapters; Places + Artefacts + Threads pre-seeded with one example each |
| `technical` | Book with Overview / Reference / Tutorials / Index chapters; markdown content-type on the Reference paragraphs |
| `nanowrimo` | Like `novel` but `word_count_goal: 50000`, `target_date: <next Nov 30>`, daily word target of 1667 pre-computed |

### 5.2 Template storage

Templates live as embedded HJSON tree definitions
inside the inkhaven binary (no external file
fetch — keeps the bin self-contained).  Each
template is roughly 50-200 lines of HJSON
describing the structure; the CLI walks it and
calls `create_node` for each entry.

### 5.3 `inkhaven template list` subcommand

Surfaces available templates with one-line
descriptions.  Future-proof for community
templates loaded from `~/.inkhaven/templates/`.

## 6. Quick win — snippet expansion (HJSON)

### 6.1 Configuration

```hjson
editor: {
  snippets: {
    enabled: true
    // Map trigger → expansion.  Triggers must end
    // with a non-word character (space, Enter,
    // punctuation) to fire; partial matches don't
    // expand on every keystroke.
    triggers: {
      "\\dt":     "{today}"
      "\\dts":    "{today:%Y-%m-%d}"
      "\\time":   "{now:%H:%M}"
      "\\sig":    "— Vladimir"
      "\\char":   "{char_lookup}"
      "\\place":  "{place_lookup}"
      "\\tdo":    "TODO: {cursor}"
      "\\tk":     "[[{cursor}]]"
    }
  }
}
```

### 6.2 Built-in placeholders

| Placeholder | Expands to |
|-|-|
| `{today}` | Today's date in the project's date format |
| `{today:%Y-%m-%d}` | Today's date in chrono format |
| `{now}` | Current local time |
| `{now:%H:%M}` | Current time, custom format |
| `{cursor}` | Position cursor here after expansion (anchor) |
| `{selection}` | Replaced by the active selection (empty if none) |
| `{paragraph_title}` | Title of the open paragraph |
| `{paragraph_slug}` | Slug of the open paragraph |
| `{char_lookup}` | Pops a Characters picker; expansion is the picked entry's name |
| `{place_lookup}` | Pops a Places picker |
| `{artefact_lookup}` | Pops an Artefacts picker |

### 6.3 Bund hook for advanced snippets

Snippet body starting with `bund:` routes
through the embedded Bund VM:

```hjson
"\\nav":  "bund:nav-stack-pop"   # custom Bund function
```

Lets power users build context-aware snippets
the built-in placeholders can't express.

### 6.4 Render behaviour

Expansion fires on the first non-word character
typed AFTER the trigger string.  Pressing
backspace inside the just-expanded text undoes
the expansion (reverts to the trigger), so the
author can recover from accidental triggers.

## 7. Quick win — AI continuation drafting

`Ctrl+V d` in the editor.

Prompt envelope:

1. **System prompt** — "continue the prose
   below in the same voice / register / pacing.
   Use the prior paragraphs as voice anchor;
   write 2-4 sentences continuing the open
   paragraph's last thought."
2. **Voice anchors** — the previous N paragraphs
   (configurable `editor.continuation_anchor_count`,
   default 3) verbatim.
3. **Source** — the open paragraph's existing
   text, ending with the cursor position
   marked.

Response wrapped in `<<<DRAFT>>>` / `<<<END>>>`;
AI pane `I` apply lifts only the draft block
and inserts at the cursor.

Pairs naturally with snippet expansion (`\tdo
\dt {cursor}` then `Ctrl+V d` from inside a
TODO).

## 8. Quick win — inline footnote / endnote

`Ctrl+V f` in the editor.

Pops a multi-line text input modal.  On commit,
inserts `#footnote[<body>]` at the cursor.
Optional flag in HJSON (`editor.footnote_style`)
to switch between Typst's `#footnote[]` and a
plain `[^id]` markdown style for projects
authoring in markdown.

Future: `Ctrl+V Shift+F` (free) for "list all
footnotes in the open paragraph" → picker that
jumps to each `#footnote[]` insertion point.

## 9. Quick win — project word-count goal + projection

### 9.1 HJSON config

```hjson
project: {
  // 1.2.14+ — overall manuscript word count goal.
  // Drives the Ctrl+V Shift+G projection modal.
  word_count_goal: 80000
  // Optional target date in ISO 8601.  Drives the
  // "days remaining" + "words/day required" lines
  // in the modal.  Leave empty to skip the
  // pacing math.
  target_date: "2026-09-01"
  // Which books contribute to the project total.
  // Empty = every user book (current default).
  // Useful when a project has a primary
  // manuscript book + reference/notes books
  // that shouldn't count toward the goal.
  counted_books: []
}
```

### 9.2 Modal

`Ctrl+V Shift+G` from any pane:

```
 ┌─ Project goal ──────────────────────────────────┐
 │                                                  │
 │   Total: 47,283 words / 80,000 goal     (59 %)   │
 │   ████████████████████░░░░░░░░░░░░░             │
 │                                                  │
 │   Target date:  2026-09-01  (92 days remaining)  │
 │   Required:     355 words / day from today       │
 │   Recent avg:   612 words / day (last 14 days)   │
 │   Projection:   complete on 2026-08-15 ✓ ahead   │
 │                                                  │
 │   Per book:                                      │
 │   · manuscript-en   42,118 (53 %)                │
 │   · manuscript-ru    5,165 ( 6 %)                │
 │                                                  │
 │   Esc to close                                   │
 └──────────────────────────────────────────────────┘
```

Pulls "recent avg" from the existing daily
event log (the same data the streak heatmap
shows).  Projection date computed by
`required_words / recent_avg` rounded up.

Verdict glyph (`✓ ahead` / `· on track` / `✗
behind`) keys off `(projection_date <=
target_date)` with a small tolerance.

## 10. Quick win — style transfer from sample text

`Ctrl+V y` in the editor.

Two-step UX:

1. Pops a paragraph picker scoped to the
   current book (any picker that yields a
   paragraph UUID works — repurpose the
   existing fuzzy paragraph picker).
2. On selection: composes a prompt envelope:

```
You are rewriting one paragraph in the style of
a reference paragraph.  Match: sentence length
distribution, vocabulary register, rhythm, mood,
narrative distance.  Preserve: literal meaning,
named entities, plot facts.

── Reference paragraph (style source) ──
{sample_body}

── Target paragraph (to rewrite) ──
{target_body}

Respond with the rewritten target paragraph
between `<<<REWRITE>>>` and `<<<END>>>` markers.
```

Streams into AI pane; standard `I` apply
extracts only the rewrite block.

Different from `Ctrl+B Shift+M` rhythm rewrite
(rhythm-only, prompt-defined target style) — this
one targets ANOTHER PARAGRAPH'S style explicitly.

## 11. Phased rollout

Anchor + quick-wins ship as ~5 phases:

| Phase | Scope | Tests |
|-|-|-|
| **A.1** | Threads system book + HJSON template + tree-pane scaffold | ~5 |
| **A.2** | Thread weave view + Ctrl+V Shift+H picker | ~3 |
| **A.3** | AI thread audit (Ctrl+V Shift+A) | ~2 |
| **C.1** | Inline comments — sidecar storage + overlay + Ctrl+V c | ~8 |
| **C.2** | Comments panel + AI digest + CLI surface | ~5 |
| **Q.1** | Project templates (CLI + 5 built-ins) | ~6 |
| **Q.2** | Snippet expansion (HJSON-driven + 9 placeholders) | ~10 |
| **Q.3** | AI continuation drafting + inline footnote (paired — they're tiny) | ~3 |
| **Q.4** | Project goal modal + style transfer (paired) | ~4 |

Total target: **~46 new tests**, on top of the
565 from 1.2.13.  Each phase ships as its own
commit; main stays green between them.

## 12. Risks + open questions

* **Thread weave view rendering** — the
  swim-lane geometry doesn't trivially scale
  past ~12 threads in the viewport.  Mitigation:
  picker first, weave as a focused sub-view of
  the picker selection (3-5 threads at a time).
* **Comment sidecar files** — git-friendly for
  single authors; multi-author needs a merge
  story.  Sidecar JSON is small + line-oriented,
  so a JSON-aware merge driver (or Bund hook on
  conflict) might be cleaner than manual
  resolution.  Phase C.2 candidate.
* **Snippet expansion vs paste / undo** — needs
  to compose cleanly with `Ctrl+Z` (undo) so
  one undo reverts the expansion, not the
  trigger characters.  Test coverage will catch
  the edge cases.
* **Project word-count goal across books** —
  `counted_books: []` default means "all user
  books" which is right for novels but wrong
  for RPG sourcebooks (where multiple manuscript-
  scale books exist).  Optional explicit list
  is the escape hatch.
* **Style transfer prompt size** — two full
  paragraphs + the system prompt typically
  lands at 2-4K tokens.  Fine.  When the
  reference and target are both chapter-scale
  (rare via this chord) the envelope grows;
  not gated for now.

## 13. Recommendation

Ship in this order:

1. **A.1 → A.2 → A.3** (Threads + weave + audit) — the headline.
2. **Q.1 (project templates) + Q.2 (snippets)** — easy wins that improve every authoring session.
3. **C.1 → C.2** (comments) — pairs with future beta-reader workflows; can ship standalone or wait for a beta-reader companion feature in 1.2.15.
4. **Q.3 + Q.4** (continuation drafting + footnote + goal modal + style transfer) — slot in opportunistically.

If the cycle runs long, the natural cut points
are: defer C.2 (panel + AI digest) and Q.4 to
1.2.15.  C.1 + A.1-3 + Q.1-3 alone is already
a substantive cycle.
