Document top-down and bottom-up writing, lazy title/abstractor/visual that persist after generation, and per-field regenerate plus lock. Wire into capability model and stage-1 notes.
9.6 KiB
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
- Body is never blocked on metadata. A page with only prose is valid.
- Display needs projections — card/list UI needs title, abstractor, visual when showing an entity.
- Projections may start virtual — generated on first need (lazy), then persisted as normal field values.
- After persistence, fields behave like user-authored data — editable, exportable in frontmatter, same SoR as hand-written values.
- Regenerate is an explicit action, not ambient rewriting.
- Lock freezes a field against automatic (and optionally against bulk) regeneration.
- 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
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
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
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
titleorabstractororvisualis 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
userorlockedvalues
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)
---
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/visualso export and non-coulomb tools still see normal fields. projections.*holds lifecycle only (state, lock, generator, hashes) — not a second title.- If
projectionsis missing (imported Bubble/markdown), treat present fields asuserunlocked; absent fields asabsent.
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
- Default generator set — deterministic only vs LLM when configured.
- Stale derived fields — badge-only vs optional auto-refresh.
- Visual generation — placeholder vs asset pipeline vs external image model.
- Outline write-back — always mutate body vs ephemeral outline panel until “Apply”.
- 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.mddocs/architecture/ArchitectureBlueprint.mddocs/capability/stage-1-cutover.mddocs/capability/existing-bubble-capability-map.md(Title, Abstractor, Visual)