From e2aa4045f45fca0e64bbc2a28bef82d479af7e8e Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 13 Aug 2026 00:07:32 +0200 Subject: [PATCH] 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. --- docs/architecture/ArchitectureBlueprint.md | 1 + .../authoring-modes-and-lazy-projections.md | 236 ++++++++++++++++++ docs/capability/model-v0.yaml | 15 +- docs/capability/stage-1-cutover.md | 3 +- 4 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 docs/architecture/authoring-modes-and-lazy-projections.md diff --git a/docs/architecture/ArchitectureBlueprint.md b/docs/architecture/ArchitectureBlueprint.md index cd4cee7..9b2411e 100644 --- a/docs/architecture/ArchitectureBlueprint.md +++ b/docs/architecture/ArchitectureBlueprint.md @@ -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` diff --git a/docs/architecture/authoring-modes-and-lazy-projections.md b/docs/architecture/authoring-modes-and-lazy-projections.md new file mode 100644 index 0000000..7eace9f --- /dev/null +++ b/docs/architecture/authoring-modes-and-lazy-projections.md @@ -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 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) diff --git a/docs/capability/model-v0.yaml b/docs/capability/model-v0.yaml index c559ecd..c0ebf65 100644 --- a/docs/capability/model-v0.yaml +++ b/docs/capability/model-v0.yaml @@ -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 diff --git a/docs/capability/stage-1-cutover.md b/docs/capability/stage-1-cutover.md index 5236123..a4516a2 100644 --- a/docs/capability/stage-1-cutover.md +++ b/docs/capability/stage-1-cutover.md @@ -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`.