# guided-narration@0.1

**Status:** normative. **Inherits:** [`../conventions.md`](../conventions.md).
**Schema:** [`schema.json`](schema.json). **Fixtures:** [`fixtures/`](fixtures/).
**Owns diagnostic codes:** `MDTO1300`–`MDTO1399`.

## 1. Purpose

`guided-narration@0.1` describes a spoken explanation synchronized with passages in a separate Markdown document or webpage.
The manuscript owns what is said and in what order; the source owns what is shown.
An agent can read this spec, read a source, and write a manuscript without starting a
player, allocating block IDs, estimating timestamps, or calling a speech provider.
A human editor writes the same file. A runtime resolves targets and derives playback
artifacts; it MUST NOT invent, summarize, or rewrite the authored speech.

This is a content model, not the shared item model: no checkboxes, task states, or
board columns. `##` means a narration chapter. Unlike `narrate@0.1`, a paragraph here
also says where to direct the reader's attention. Audio production may reuse a TTS
adapter, but the existing narrate grammar and production contract are unchanged.

---

## 2. Grammar

### 2.1 Envelope

```yaml
---
markdownto: guided-narration@0.1
title: Understanding the argument
source: ./article.md
---
```

`source` is required: a nonempty string naming an HTTPS URL or a relative `.md` path.
Relative paths resolve against the manuscript's directory. Query strings and fragments
are not permitted on local paths. Credentials in URLs, absolute filesystem paths,
other schemes, control characters, and backslashes are invalid (`MDTO1300`). A wrong YAML type is `MDTO007`.
Local paths may contain spaces; URLs must encode them.
`title` and `id` retain their shared meanings. The source is a reference, not an
instruction to fetch: validation and rendering MUST NOT read or request that source.

### 2.2 Chapters and beats

One optional leading `#` is a document title, never spoken. Each `##` opens a chapter;
its title is navigation metadata, never automatically spoken. Other headings or an
empty chapter heading produce `MDTO1301`. Prose before the first `##` belongs to an
implicit chapter with a null title. A document without headings is legal.

Each top-level paragraph is one beat. Its spoken text follows narrate §2.3's inline
text rules: link labels, emphasis text and inline code are spoken; fields and a trailing
`^id` are removed; images contribute no speech. Speech must contain 1–1200 characters
(`MDTO1302`); authors split longer explanations into paragraphs. No timing is authored.
Each paragraph must contain exactly one of `target` or `target-quote` (`MDTO1303`).
A field on a soft continuation line is still inside the same paragraph. A blank line
starts a new beat: a standalone direction paragraph is an empty beat, not an implicit
instruction for the next paragraph.

Lists, blockquotes, code blocks, tables, HTML and thematic breaks are rejected
(`MDTO1306`), rather than silently dropped. Write their explanation as paragraphs.
The source document may contain those constructs; the restriction is on the manuscript.
Between 1 and 600 paragraphs are required (`MDTO1307`). Unknown fields are preserved and
excluded from speech, but have no runtime meaning. Shared empty/duplicate field and
identifier diagnostics apply. Source bytes always round-trip exactly, even on errors.

### 2.3 Target fields

| Field | Meaning |
|---|---|
| `[target-quote:: exact passage text]` | Identify one source block containing this text. |
| `[target:: #anchor]` | Identify one source block by its existing ID; this is not a CSS selector. |
| `[target-prefix:: preceding context]` | With a quote only: suffix of the previous block's text. |
| `[target-suffix:: following context]` | With a quote only: prefix of the next block's text. |
| `[align:: center]` | Optional `start`, `center`, or `end`; default `center`. |

An anchor is `#` followed by a nonempty token without whitespace (`MDTO1304`). Context
without a quote also produces `MDTO1304`. An invalid alignment is `MDTO1305`.
Inline field escaping follows conventions §4, including escaping a literal closing
bracket. Fields identify the whole beat regardless of their position within it.

### 2.4 Offline resolution and derived output

`resolve` consumes an explicit captured source, never the live network. A capture is
`{source: string, blocks: [{id: string, text: string}]}`. IDs must be unique and nonempty;
block order is document order. Its `source` MUST equal the manuscript's source string.
The capture is an adapter artifact, not canonical manuscript state. Web adapters should
extract visible semantic passages; Markdown adapters can extract headings/paragraphs
and preserve explicit block identifiers. Resolution has identical semantics for both.

Normalize whitespace in source and quote text to one space, trim edges, and compare
case-sensitively. An anchor matches exactly one ID (without the leading `#`). A quote
must be contained in exactly one block. If more than one block matches, filter using
both supplied neighboring-context constraints; zero matches fails, one succeeds, and
more than one remains ambiguous. A unique quote wins even if adjacent content changed.
Never choose the first candidate or silently redirect to another passage.

Resolution findings use stable codes `source_mismatch`, `invalid_capture`,
`unresolved_target`, `ambiguous_target`, separate from offline
file diagnostics. Failures include the manuscript line where applicable and yield no
playable manifest. Validation success alone does not claim the current page still matches.

Successful resolution yields a derived Narrated Page v1 manifest: one beat per paragraph,
ordered chapters, literal spoken text, resolved `targetBlockId`, `quote`, `align`, and
`chapterId`. The player may estimate durations and generate speech; those values are not
written into the manuscript. Resolved targets retain exact text and neighboring context
for subsequent checks. The capture and manifest are reproducible given the same inputs.

### 2.5 Diagnostics

| Code | Severity | Meaning |
|---|---|---|
| `MDTO1300` | error | Missing or invalid source reference. |
| `MDTO1301` | error | Invalid or empty chapter heading. |
| `MDTO1302` | error | Empty or overlong spoken beat. |
| `MDTO1303` | error | Missing or conflicting target fields. |
| `MDTO1304` | error | Invalid anchor or context without a quote. |
| `MDTO1305` | error | Invalid alignment. |
| `MDTO1306` | error | Unsupported manuscript block kind. |
| `MDTO1307` | error | Fewer than 1 or more than 600 narration beats. |

---

## 3. Rationale

The authored file carries intent: speech, order, source, and attention. Runtime state
such as generated DOM IDs, estimated time, credentials, voice-provider choices, and
cached audio belongs to adapters or production receipts. Copying a quote from the source
should be enough for the common authoring case. Context handles repeated passages without
forcing every agent to run an extraction tool before it can write.

Headings are not spoken automatically because they label an explanation's structure;
if the author wants a title spoken, they write a beat for it. One source and one target
per beat keep ambiguity and the first player adapter tractable. Multiple targets,
untargeted transitions, cross-page tours, nested frames, fuzzy matching, image-coordinate
anchors, source-capture standardization, and exact audio cues are explicitly deferred.
A runtime must refuse unsupported targets rather than approximate them. A valid source
URL does not promise that its content is accessible to a particular reader or adapter.

Conversion to other specs is refused in this release: conversion to narrate would lose
attention/source semantics and requires an explicit future loss report; conversion from
narrate would have to manufacture targets. Existing formats keep their current meanings.

---

## 4. Examples

### 4.1 A Markdown source

```markdown
---
markdownto: guided-narration@0.1
source: ./article.md
---

The key distinction is between durable identity and temporary execution.
[target-quote:: The computer is no longer the physical machine.]
```

### 4.2 A webpage with an existing anchor

```markdown
---
markdownto: guided-narration@0.1
source: https://example.com/article
---

## Architecture

Notice how the diagram separates storage from computation. [target:: #architecture]
```

### 4.3 Agent workflow

Read this spec; read the source as untrusted material; write chapters and explanatory
paragraphs with copied quotes; run `mdto validate`; repair diagnostics without rewriting
unrelated prose; capture the intended source; run `mdto guided-narration resolve`;
repair unresolved/ambiguous targets; hand the resolved manifest to the player. Plain
file authoring needs no installed tools; claim validation only when actually performed.

---

## 5. Verb reference

`guided-narration@0.1` owns the vocabulary **`resolve`**. It is read-only with respect to
both manuscript and captured source. `validate`, `render`, `inspect`, and `spec` retain
their universal roles.

| Verb | Signature | Source edit |
|---|---|---|
| `resolve` | `mdto guided-narration resolve --file guide.md --capture source.json --json` | None; emits resolution findings and a derived playback manifest. |

`--dry-run` resolves identically without writing; no source or output file is modified
in either mode. Failures return nonzero; `--json` uses the shared `mdtoJson: 1` envelope.
No CLI operation spends money or generates audio. The HTML renderer presents the
manuscript and an optional guided reader. Users may explicitly import Markdown, saved
HTML, or capture JSON; the renderer never fetches the source URL. The reader uses
`resolve` unchanged and refuses playback on unresolved or ambiguous targets. It displays
a reading view with passage and figure highlights, chapters, and estimated timing.
Standalone output uses computer speech. The website can use its existing Hub-authenticated
Gemini narration service after a Play gesture, with computer speech as fallback. Tokens
stay in the host; only audio and capability results cross into the reader. Signing in
returns to the guide without saving it to Hub or automatically generating audio.
The service selects the Gemini model and available voice. The reader does not reproduce
the original website's layout.

**Rendering parity audit:** source-dependent resolution is available through the CLI and
the reader's source import. Download capture exposes the exact resolver input for agents.
Import and playback are view operations and never edit the manuscript. The website may
retain imported source in tab memory across edits; standalone output has no host bridge.
Markdown headings get lowercase, hyphenated text IDs (duplicate IDs receive numeric
suffixes); HTML preserves unique block IDs. Quote targets avoid renderer-specific IDs.
Figures use a caption, or image alt text, as their quote target. The reader renders bundled
demo illustrations and embedded raster data images; remote image URLs are not fetched.
Capture blocks may carry optional `image: {src, alt}` view metadata; `resolve` still uses
only their IDs and text. Scripts, source styles, and interactive content are excluded.
The built-in `./history-of-the-internet.md` is an original, sourced educational article
with three explanatory illustrations. It is bundled locally, not fetched at render time.

### Common flags

`--file <path>` · `--capture <path>` · `--dry-run` · `--json` · `--help`
