Design dual authoring modes and lazy projection fields

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.
This commit is contained in:
tegwick 2026-08-13 00:07:32 +02:00
parent 64d4dcda7c
commit e2aa4045f4
4 changed files with 253 additions and 2 deletions

View file

@ -272,6 +272,7 @@ See **[spike-checklist.md](./spike-checklist.md)** — executable evaluation tas
- `docs/architecture/2026-08-12-content-substrate-exploration.md`
- `docs/architecture/transclusion-and-chunks.md` — page-first model; chunks as extract/transclude
- `docs/architecture/authoring-modes-and-lazy-projections.md` — top-down/bottom-up; lazy Title/Abstractor/Visual + lock
- `docs/capability/stage-1-cutover.md`
- `docs/decisions/2026-08-12-feature-cut-stage-1.md`
- kontextual: `docs/architecture-blueprint.md`, `asset-registry-implementation.md`, `blob-storage-content-streaming-workplan.md`, `markitect-tool-reuse-boundary.md`

View file

@ -0,0 +1,236 @@
# 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 12 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** pages 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 pages** 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 “users 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)

View file

@ -99,11 +99,24 @@ capabilities:
status: partial
notes: "Inferred from H1/filename; frontmatter/UI todo"
- id: feat.page-abstract
title: Page Abstract
title: Page Abstractor
status: todo
- id: feat.page-visual
title: Page Visual
status: todo
- id: feat.authoring-top-down
title: Top-down authoring (title → abstractor → outline → body)
status: todo
notes: "docs/architecture/authoring-modes-and-lazy-projections.md"
- id: feat.authoring-bottom-up
title: Bottom-up authoring (body first; metadata later)
status: todo
- id: feat.projection-lazy-fill
title: Lazy-generate missing title/abstractor/visual on display
status: todo
- id: feat.projection-regenerate-lock
title: Per-field regenerate and lock for projections
status: todo
- id: feat.content-fail-closed
title: Fail closed on missing binding/fetch
status: shipped

View file

@ -124,4 +124,5 @@ capabilities but does not redefine the must set.
3. **Page body editing** — stage 1: product forms for Title/Abstract/Visual + markdown body path; exact editor TBD.
4. **Delete** — hard delete vs archive (`is_active`)?
5. **Transfer cohort** — require public self-registration, or LLDAP bulk invite for stage 1?
6. **Content substrate** — see `docs/architecture/2026-08-12-content-substrate-exploration.md` (markitect-tool + markdown SoR; evaluate shard-wiki thin slice vs folder/git store).
6. **Content substrate** — see `docs/architecture/2026-08-12-content-substrate-exploration.md` (markitect-tool + markdown SoR; evaluate shard-wiki thin slice vs folder/git store).
7. **Authoring** — top-down and bottom-up both first-class; lazy title/abstractor/visual with regenerate + lock — `docs/architecture/authoring-modes-and-lazy-projections.md`.