# Requirements Archive: v1.4 v1.4

**Archived:** 2026-07-01
**Status:** SHIPPED

---

# Milestone v1.4 Requirements — Editor Enhancements & Authoring Polish

## Overview

Make the page editor a richer, lower-friction authoring surface: the agent generates the page
description (no manual field), tags become first-class via inline `#tag` + LLM suggestions
(the manual Tags field is dropped), fenced code gets language-aware syntax highlighting,
` ```mermaid ` blocks render as diagrams, and Obsidian-style page templates speed up new pages.

**Locked design decisions (from kickoff + research + roadmap red-team):**
- **Tag source-of-truth = "frontmatter-canonical" (the model called Model B in SUMMARY/this doc;
  note `PITFALLS.md` uses swapped A/B labels — the *frontmatter-canonical, sync-at-save* model is
  the chosen one regardless of label).** Inline `#tag` is the authoring surface; on save the
  **client** unions/reconciles body `#tags` into the frontmatter `tags:` string it already sends
  (byte-stable through the normal save's `okf.Emit`). Search, graph, tag-sweep, and the deferred
  v1.3 tag-management keep reading frontmatter ONLY — zero indexer change. There is **no second
  server-side `#tag` parser** (avoids the client/server drift PITFALLS 1.4 warns about).
- **All agent writes stay propose/suggest → review → approve** (no silent writes), byte-stable.
- **Mermaid is the only new runtime dependency** — lazy `import()` chunk, `securityLevel:'strict'`.
- **Syntax highlighting is CM6-native** (`highlightCode` + opt-in, code-split `@codemirror/lang-*`),
  one parser set for both the live editor and the read `CodeBlockWidget`. No Shiki/highlight.js/Prism.
- **v1.4 scope is the CM6 surfaces** (live + read). Rendering `#tag` badges in the react-markdown
  path (agent chat / diff preview) is deferred (Future).

Phases continue from v1.3's last (Phase 20) → **v1.4 spans Phases 21–27** (7 phases).

---

## ED-01 — LLM-Generated Description

### ED-01a: Remove the manual Description field
**Requirement:** The page editor no longer shows a hand-entered Description input.
**Acceptance criteria:**
- [ ] The Description `<input>` is removed from `PageEditor.tsx`; no editor regression.
- [ ] The frontmatter `description` key still exists on every page (`okf.Repair` is unchanged — it only *adds* a missing key, never overwrites; verified `okf.go:24-37`, `repair.go:43-49`).
- [ ] An existing human-written description is preserved (never blanked by removing the field).

### ED-01b: Agent generates the description on demand
**Requirement:** An editor can ask the agent to generate (or refresh) the page description.
**Acceptance criteria:**
- [ ] A "Generate description" affordance triggers a single-shot agent call over the page body.
- [ ] Generated text is a short plain-text meta description (~1 sentence, soft cap ~160 chars), shown for review BEFORE apply, editable + regenerable.
- [ ] Approve writes `description` via byte-stable `okf.SetField` through the revision-checked save (409 on a moved revision).
- [ ] Generation is **on demand only** — never auto-runs on save or create (no silent write).
- [ ] Output is re-validated server-side before write.
- [ ] **Autosave reconciliation:** after approving a description, the editor reseeds its in-memory `description` AND advances `baseRevision` atomically, so a subsequent body autosave **neither 409s into the conflict dialog nor reverts the just-applied description** (the `seeded`-guard / stale-frontmatter contention the red-team flagged). Criterion: "approve description → keep typing body → autosave succeeds, description intact."
**Security:** editor-gated apply; CSRF; page content untrusted to the prompt.

---

## ED-02 — Inline `#tag` Badges (foundational)

### ED-02a: Parse and render inline `#tag` as badges  *(Phase 21)*
**Requirement:** `#tag` tokens typed in the page body render as labeled badges on the live-preview and read surfaces — decoration-only, byte-stable.
**Acceptance criteria:**
- [ ] A `@lezer/markdown` inline-parser extension defines a `Hashtag` node (composed with the existing GFM extensions in `markdown.ts`).
- [ ] `#tag` renders as a badge in live preview and read (clean-render) mode; the raw `.md` keeps the literal `#tag` (decoration-only → the document is never mutated). Source mode + the active editing line show the raw `#tag`.
- [ ] Tag syntax (Obsidian): alphanumerics + `_` `-` and `/` (nested `#a/b`); ≥1 non-digit; no spaces; case-insensitive match, original-case display.
- [ ] `#` is NOT a tag inside fenced code, inline code, an ATX heading (`# Title`), a URL fragment (`/#frag`), an all-numeric run (`#1984`), when escaped (`\#`), or mid-word (`foo#bar`). **`#`-prefixed hex colors ARE valid tags** (`#abc`/`#fff` are legitimate short tags and indistinguishable from hex without context — follow Obsidian, do not special-case hex). Pinned by `livePreview.test.ts` fixtures.
- [ ] The badge widget DOM is built with `createElement` + `textContent` only (no `innerHTML`).
**Security:** decoration/widget only — no document transaction; no HTML injection from tag text.

### ED-02b: Reconcile inline tags into frontmatter on save  *(Phase 22)*
**Requirement:** Saving a page reconciles its body `#tags` into the canonical frontmatter `tags:` — additions AND removals — **client-side** (reuse the Lezer parse; persist through the normal save).
**Acceptance criteria:**
- [ ] On save the client computes: `removedInline = inlineTags(lastSavedBody) − inlineTags(currentBody)`, then `frontmatter.tags = (priorFrontmatterTags − removedInline) ∪ inlineTags(currentBody)`, written via the existing byte-stable client `setTags` into the frontmatter string the save already sends (`lastSavedBody` is the existing `PageEditor` ref). NOT a monotonic union (which can't remove).
- [ ] Adding `#deploy` to the body → frontmatter gains `deploy`; **deleting** `#deploy` from the body → frontmatter loses `deploy`; an **LLM-applied tag never written inline persists** across the save.
- [ ] Search, graph (`page_tags`), tag-sweep vocabulary, and v1.3 tag-management read frontmatter unchanged (no indexer change).
- [ ] Normalization matches existing tag rules (trim, case-fold, case-collision → one tag); server re-validates before write.
- [ ] A golden test exercises add + remove + LLM-tag-persists in a single save.

### ED-02c: Remove the manual Tags chip field; minimal removal control  *(Phase 22)*
**Requirement:** The chip Tags field is replaced; LLM/imported (non-inline) tags remain removable.
**Acceptance criteria:**
- [ ] The `InlineTagEditor` chip field is removed from `PageEditor.tsx`.
- [ ] A minimal read-only badge bar shows the page's effective tags. **Non-inline tags = `frontmatterTags − inlineTags(body)`**; each gets an ✕ to remove it (byte-stable frontmatter write). Inline-origin tags are managed by editing the body (`#tag`) and carry no ✕.

---

## ED-03 — LLM Tag Suggestions  *(Phase 23)*

### ED-03a: Suggest tags into the canonical tag set
**Requirement:** An editor can get agent tag suggestions that feed the frontmatter-canonical tag set.
**Acceptance criteria:**
- [ ] Reuses the shipped `SuggestTags` agent mode + `apply-tags` path (vocabulary-biased, capped, server-revalidated).
- [ ] Suggestions are presented for approval (no silent writes); approved tags land in **frontmatter `tags:`** — never inserted as `#tag` into the user's prose.
- [ ] De-duped (case-insensitive) against the **union** of body inline tags + frontmatter tags — no duplicate of an existing tag.
- [ ] A suggestion failing tag-charset validation is rejected.
**Security:** editor-gated apply; CSRF; page content untrusted to the prompt.
**Depends on:** Phase 22 (the tag model + reconciliation).

---

## ED-04 — Code Syntax Highlighting  *(Phase 25)*

### ED-04a: Highlight fenced code on both CM6 surfaces
**Requirement:** Fenced code renders with language-aware highlighting in the live editor and the read widget, **without** statically bloating the main bundle.
**Acceptance criteria:**
- [ ] One shared `HighlightStyle` (token colors from design tokens, light/dark aware) drives both surfaces.
- [ ] Live editor: highlighting via `markdown({ codeLanguages })`; read `CodeBlockWidget`: via `highlightCode` from `@lezer/highlight`, emitting spans through `createElement` + `textContent` (no `innerHTML`).
- [ ] Languages are **code-split** (`LanguageDescription.of({ load: () => import(...) })`). Because `CodeBlockWidget.toDOM` is synchronous, the read widget renders plain first and **re-highlights when the language parser resolves** (the async-widget pattern) — languages are NOT static-imported into the main chunk.
- [ ] Curated v1.4 language set: js/ts, python, go, rust, json, yaml, sql, html, css, bash, markdown, diff (bash/diff/dockerfile via `@codemirror/legacy-modes`). Unknown/absent language → plain monospace, no error.
- [ ] **Bundle budget:** the main editor chunk grows by no more than an agreed budget (the highlight core + style, not the per-language grammars); verified against the build output. A ` ```mermaid ` fence is routed to ED-05, not highlighted.
**Security:** highlighter output is token spans built with textContent only (no HTML strings).
**Out of scope (this feature):** line numbers; a copy-code button (deferred — see Future).

---

## ED-05 — Mermaid Diagrams  *(Phase 26)*

### ED-05a: Render ` ```mermaid ` blocks as diagrams
**Requirement:** Fenced `mermaid` blocks render as diagrams; Mermaid never enters the main bundle.
**Acceptance criteria:**
- [ ] A `MermaidWidget` (block widget from a StateField, branching `codeBlockField` on `CodeInfo === "mermaid"`) renders flowchart/sequence/class/state/ER at minimum.
- [ ] `mermaid` is loaded via dynamic `import()` (own async chunk). **An automated build assertion** (not eyeballing) verifies `mermaid` is absent from the main editor chunk.
- [ ] Rendered with `securityLevel:'strict'`; SVG injected via DOMParser + appendChild (never `innerHTML` of page content).
- [ ] A parse/render error falls back to the raw fenced code block (existing `CodeBlockWidget`) — never blank, never a crash.
- [ ] Light/dark theme honored; live-preview re-render is debounced (not per-keystroke).
**Security:** strict sandbox + sanitized SVG; client-only render (no SSR/CDN); untrusted diagram source.
**Depends on:** Phase 25 (shares `widgets.ts`/`livePreview.ts`).

---

## ED-06 — Page Templates  *(Phase 27)*

### ED-06a: Templates stored as files, picked on new-page
**Requirement:** Creating a page can seed it from an Obsidian-style template.
**Acceptance criteria:**
- [ ] Templates are plain `.md` files in a `Templates/` folder in the repo (files-as-truth; git-versioned; editable as normal pages).
- [ ] A list endpoint enumerates templates; the create-page flow offers a picker (blank/no-template default remains).
- [ ] The chosen template's frontmatter + body seed the new page; `{{title}}`/`{{date}}`/`{{time}}` substituted (`String.replaceAll` + `Intl.DateTimeFormat`; no templating engine).
- [ ] **Frontmatter merge is robust:** a template that carries its OWN `---` frontmatter does not produce two frontmatter blocks, duplicate keys, or malformed YAML — the template's frontmatter is merged into the new page's single frontmatter, then required-field repaired (PITFALLS 5.2). Adversarial test included.
- [ ] Template paths resolve through the existing fuzz-tested safe path resolver.
**Security:** no executable/JS templates (static text + the three safe `{{…}}` placeholders only); path-safety on template reads.
**Out of scope (this feature):** insert-at-cursor into an existing page (deferred — see Future).

---

## Future Requirements (deferred)

- `#tag` badge rendering in the **react-markdown** path (agent chat / diff preview).
- Copy-code button + language-label header on code blocks.
- Templater-style per-folder default templates; insert-at-cursor into existing pages.
- Description surfaced in search-result cards / graph hover (consumer wiring).

## Out of Scope (explicit)

- **The Obsidian-literal model where the indexers parse body `#tags`** — too large a blast radius into shipped search/graph/sweep and the v1.3 tag-management contract.
- **A second server-side Go `#tag` parser** — client/server drift risk; materialization is client-side.
- **Auto-generating the description on save/create** — silent write; violates the approval model.
- **Executable/script (Templater-style) templates** — code-execution surface.
- **Line numbers / full 300-language Prism set** in code blocks — noise / YAGNI.
- **A second Markdown engine or Shiki-rendered read surface** — keep CM6 `cleanRender` canonical.
- **A `#hex`-color exclusion in the tag grammar** — would kill valid short tags; not done.

## Traceability

| REQ-ID | Phase | Status |
|--------|-------|--------|
| ED-02a | 21 | planned |
| ED-02b, ED-02c | 22 | planned |
| ED-03a | 23 | planned |
| ED-01a, ED-01b | 24 | planned |
| ED-04a | 25 | planned |
| ED-05a | 26 | planned |
| ED-06a | 27 | planned |
