# Authoring modes and lazy projection fields | Field | Value | |-------|--------| | Date | 2026-08-13 | | Status | design proposal (product + data model) | | Related | page-first model, Abstractor/Title/Visual, stage-1 cut, markitect structure | ## Intent Coulomb should make **both** styles of knowledge work feel natural: | Mode | Flow | |------|------| | **Top-down** | Title → short description (abstractor) → outline → elaborate body → optional visual | | **Bottom-up** | Plunge into body text → title / abstractor / structure / visual emerge after or while writing | Neither mode may force the other. Empty or content-only entities must still **display** as first-class cards (title, description, illustration) without blocking the writer. ## Principles 1. **Body is never blocked** on metadata. A page with only prose is valid. 2. **Display needs projections** — card/list UI needs title, abstractor, visual when showing an entity. 3. **Projections may start virtual** — generated on first need (lazy), then **persisted as normal field values**. 4. **After persistence, fields behave like user-authored data** — editable, exportable in frontmatter, same SoR as hand-written values. 5. **Regenerate** is an explicit action, not ambient rewriting. 6. **Lock** freezes a field against automatic (and optionally against bulk) regeneration. 7. **Structure stays page-first** — outline is headings/sections in the markdown body (or a derived outline view), not a separate chunk ontology. See `transclusion-and-chunks.md`. --- ## Field set (stage-1 aligned) | Field | Role | Display use | |-------|------|-------------| | **title** | Name of the entity | Card headline, browser title, lists | | **abstractor** | Short description (Bubble: Abstractor) | Card subtitle, previews, search snippet | | **visual** | Illustration / cover | Card face, headers | | **body** | Main markdown content | Full page view | | **outline** | Derived (usually) from body headings | Top-down scaffold; navigation | `outline` is typically **not** a separate SoR blob: it is **read from** `#` / `##` structure, or optionally written back as a stub section when the user accepts a generated outline in top-down mode. --- ## Dual mode UX ### Top-down path ```text New page → focus title (optional visual placeholder) → abstractor → "Generate outline" → body becomes heading skeleton → user elaborates under headings → visual optional (upload or generate) ``` - Empty body with title/abstractor is fine. - Outline generation **writes body** (or a dedicated outline region) only when the user asks — not on every keystroke. ### Bottom-up path ```text New page / open blank body → write freely → on first card/list display (or idle debounce): lazy-fill missing title/abstractor/visual → user may edit, regenerate, or lock any field → optional "Propose outline" restructures headings without deleting prose carelessly ``` - Creating a page must not require a title dialog. - Autosave body continuously; projections fill **lazily on display need** or on explicit "Suggest metadata". ### Mode switching There is **no mode toggle the user must set**. The product infers intent: - If title/abstractor present and body empty → treat as top-down. - If body present and title empty → bottom-up; lazy projections allowed. - Both filled → normal edit; regenerate only on demand. --- ## Lazy projections (virtual → real) ### Lifecycle of a projection field ```text missing → (display needs field) generate candidate [virtual / ephemeral] → persist to page frontmatter [real] → user edit | regenerate | lock ``` | State | Meaning | |-------|---------| | **absent** | No value in SoR; may generate | | **derived** | Value present; `provenance: generated` (or equivalent); unlocked | | **user** | Value present; last material edit was human (or import); unlocked | | **locked** | Value fixed; no auto/lazy regenerate; explicit unlock required | **Rule:** Once generated and saved, the value is **kept** as if the user had typed it — cards and export see real frontmatter, not a live prompt every time. **Lazy trigger examples:** - First render of a card in a list/deck when `title` or `abstractor` or `visual` is absent - Opening page header chrome when fields missing - Explicit “Fill missing metadata” **Not triggers (by default):** - Every keystroke in the body - Background jobs that rewrite locked or recently user-edited fields - Silent overwrites of `user` or `locked` values ### Generation sources (implementation-agnostic) | Field | Cheap deterministic options | Richer options (optional) | |-------|----------------------------|---------------------------| | title | First H1, first non-empty line, filename/slug | LLM short title from body | | abstractor | First paragraph, first section under H1 | LLM 1–2 sentence summary | | visual | Palette placeholder from hash of id; first image in body/assets | Image model / pick from assets | | outline | Existing headings; or insert H2 stubs from summary | LLM outline from abstractor or body | Prefer **deterministic first** for offline/dev and predictable tests; LLM behind the same regenerate/lock contract. ### Persist shape (illustrative frontmatter) ```yaml --- id: page-… title: "Working title" abstractor: "One-line sense of the page." visual: "assets/visuals/page-….webp" # or sha256:… asset ref projections: title: state: derived # absent | derived | user | locked locked: false generated_at: "2026-08-13T12:00:00Z" generator: "first-h1@v1" source_hash: "sha256:…" # body hash when generated — optional staleness hint abstractor: state: user locked: false visual: state: locked locked: true generated_at: "2026-08-13T11:00:00Z" generator: "placeholder-hash@v1" --- Body markdown… ``` Notes: - **Display values** live in top-level `title` / `abstractor` / `visual` so export and non-coulomb tools still see normal fields. - **`projections.*`** holds lifecycle only (state, lock, generator, hashes) — not a second title. - If `projections` is missing (imported Bubble/markdown), treat present fields as `user` unlocked; absent fields as `absent`. ### Actions on each field | Action | Behavior | |--------|----------| | **Edit** | Set value; set state → `user`; clear stale generator metadata or keep last generator for audit | | **Regenerate / Update** | Allowed if not locked; re-run generator; overwrite value; state → `derived`; refresh `generated_at` / `source_hash` | | **Lock** | `locked: true`, state → `locked`; blocks regenerate and lazy auto-fill | | **Unlock** | `locked: false`; state → `user` or `derived` (keep last provenance) | UI: per-field overflow (✎ edit · ↻ regenerate · 🔒 lock) on cards and page chrome — unobtrusive, always available. ### Staleness (optional, unlocked derived only) If `source_hash` ≠ current body hash, UI may show “metadata may be outdated” and offer regenerate — **never** auto-regenerate locked or `user` fields without consent. Policy for `derived` + stale can be: badge only (default) or auto-refresh if product later opts in. --- ## Interaction with page-first / transclusion - **Outline** = structure of **this** page’s body (headings), not a chunk table. - Top-down “generate outline” writes headings into body; elaboration is normal writing under those headings. - Bottom-up “propose outline” may reorganize into headings (explicit action; preview/diff recommended). - A card for a **transcluded excerpt** uses the **source page’s** projections unless the excerpt is promoted to its own page. --- ## Capability / feature mapping (rebuild) Under `cap.space-content` (and any entity shown as a card): | Feature id (proposed) | Description | |----------------------|-------------| | `feat.authoring-top-down` | Title/abstractor/outline-first create path | | `feat.authoring-bottom-up` | Body-first create; no required metadata gate | | `feat.projection-lazy-fill` | Lazy generate missing title/abstractor/visual on display | | `feat.projection-persist` | Persist generated values into frontmatter | | `feat.projection-regenerate` | Explicit per-field regenerate | | `feat.projection-lock` | Per-field lock against auto/regenerate | Stage-1: ship dual authoring + lazy title/abstractor at minimum; visual generation can start as placeholder art; LLM optional. --- ## Anti-patterns | Avoid | Why | |-------|-----| | Requiring title before first save | Blocks bottom-up | | Regenerating on every body keystroke | Noisy, fights the writer | | Virtual-only fields that never save | Breaks export and “user’s data” | | One global “AI mode” toggle for the whole page | Prefer per-field lock/regenerate | | Separate chunk entities only to hold outline | Outline is page structure | --- ## Open product choices 1. **Default generator set** — deterministic only vs LLM when configured. 2. **Stale derived fields** — badge-only vs optional auto-refresh. 3. **Visual generation** — placeholder vs asset pipeline vs external image model. 4. **Outline write-back** — always mutate body vs ephemeral outline panel until “Apply”. 5. **Space-level projections** — same contract for space cards (title/abstractor/visual). --- ## One-liner **Top-down and bottom-up are both first-class; display fields can be born lazy, then live as ordinary locked-or-editable metadata so tooling never blocks the writing.** ## Related - `docs/architecture/transclusion-and-chunks.md` - `docs/architecture/ArchitectureBlueprint.md` - `docs/capability/stage-1-cutover.md` - `docs/capability/existing-bubble-capability-map.md` (Title, Abstractor, Visual)