coulomb-social/docs/adr/ADR-0003-page-centric-markdown-sor.md
tegwick ce65b359af Add ADR-0003 and ADR-0004 for page SoR and thin git content plane
Accept page-centric markdown SoR with dual authoring, live includes, and
lazy projections; accept thin dir+git plane with PageOps and supported
upgrades to shard-wiki and kontextual. Demote ADR-0002 Forgejo-as-SoR.
2026-08-13 10:04:46 +02:00

169 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: ADR-0003
title: Page-centric markdown SoR, authoring, and live derived content
status: accepted
date: 2026-08-13
deciders: bernd
related:
- ADR-0001
- ADR-0002
- ADR-0004
- docs/architecture/ArchitectureBlueprint.md
- docs/decisions/2026-08-12-feature-cut-stage-1.md
workplans:
- CSOC-WP-0004
- CSOC-WP-0006
- CSOC-WP-0001
---
# ADR-0003 — Page-centric markdown SoR, authoring, and live derived content
## Status
**Accepted** (2026-08-13).
Supersedes the parts of **ADR-0002** that treated “one Forgejo repo per space
as the product SoR.” ADR-0002 remains valid as a **historical vertical slice**
and as **one optional git/Forgejo binding shape** under ADR-0004.
## Context
The rebuild must exit Bubble with **user-owned content** that is easy to
export and reuse. Live coulomb models spaces, pages, and chunks; stage-1 cut
requires Title, Abstractor, Visual, copy, and transfer without recreating
Bubbles full chunk graph.
Prior work delivered a Forgejo-bound read/write slice (ADR-0002). Architecture
discussion established:
- Markdown (+ assets) as exportable system of record; database as cache.
- **markitect-tool** as structure library (not product host); markitect-main avoided.
- Page-first knowledge work with transclusion rather than a priori chunks.
- Dual authoring (top-down and bottom-up) and lazy display fields.
- Live includes by default (spreadsheet-like); pin as exception.
## Decision
### 1. System of record
1. **Content SoR** is **markdown files** (frontmatter + body + backmatter) and
**assets** under a **directory tree** versioned in **git** (see ADR-0004).
2. **Postgres** (or any app DB) holds only:
- identity linkage (`Member` ↔ NetKingdom subject),
- product indexes / **projections** for cards and lists,
- optional pointers into the tree (paths, digests) —
**not** long-form page bodies as SoR.
3. Anything a user should take on export must live in the tree; DB content for
bodies is disposable and rebuildable.
### 2. Information model
| Concept | Meaning |
|---------|---------|
| **Page** | Primary knowledge object: one markdown document (or connected set after explode). |
| **Space** | Container / context for pages (directory + metadata; may itself be a page of `type: space`). |
| **Card** | UI projection only (title, abstractor, visual, actions) — not a storage type. |
| **Region / “chunk”** | Addressable unit *within* a page (heading, named region, selector). **Not** a required first-class storage row. |
| **Member / tenant** | NetKingdom + app session (ADR-0001); not stored as page content. |
**Stage-1 fields** on pages and spaces (frontmatter or equivalent):
- **title**
- **abstractor** (short description; Bubble “Abstractor”)
- **visual** (ref to an asset)
- **body** (markdown)
### 3. Page-first structure (not a priori chunks)
1. Authors write **pages** fluently; structure is headings and optional named regions.
2. Reuse uses **address → live transclude → promote** (markitect include/selector/region), not a mandatory `Chunk` entity graph.
3. Bubble `custom.chunk` migration **prefers** sections/regions or connected pages with includes — not permanent recreation of chunk tables.
4. Outline flexibility uses markitect **explode / implode** (and related compose/include): split one document into smaller **connected** documents; merge selected docs back into one. Connectivity after explode prefers **live** includes or content references.
### 4. Dual authoring modes
Both are first-class; no forced mode toggle:
| Mode | Flow |
|------|------|
| **Top-down** | Title → abstractor → outline (headings) → elaborate body → optional visual |
| **Bottom-up** | Body first; title / abstractor / structure / visual established later or while writing |
Creating or saving content **must not** require a complete metadata form.
### 5. Lazy projections, lock, and staleness
For display fields (title, abstractor, visual) and other **derived** values:
1. If **absent** and display needs them → **generate lazily****persist** as normal field values (as if user-provided for export).
2. Per-field **regenerate** and **lock** (lock blocks auto-fill and regenerate until unlock).
3. If inputs change and the field is unlocked derived → **May be stale** badge → **Generate updated version** → side-by-side **Current | Alternative****Keep current** or **Use alternative** (replace). Optional ack updates fingerprint without changing text.
4. **Never** silently overwrite **locked** or last-**user**-edited values without consent.
Generator policy: deterministic first (e.g. first H1, first paragraph); optional LLM/pipeline behind the same contract.
### 6. Live by default; pin as exception
| Mode | Behavior |
|------|----------|
| **Live (default)** | Includes and formula-like derived content resolve to **current** inputs on view/export (spreadsheet metaphor). |
| **Pinned (exception)** | Explicit freeze of a snapshot in the host document; refresh uses stale → alternative → replace \| keep. |
Pure live includes do not require replace/keep; pinned/`mkt:derived` (markitect proposal) and projection fields do.
### 7. markitect-tool role
1. coulomb **depends on markitect-tool** for parse, frontmatter, query/extract, **live include**, explode/implode, and (when implemented) derived-slot stale/refresh APIs.
2. coulomb does **not** “run on” markitect as a product platform; markitect-main is not a runtime foundation.
3. Cross-repo: pin/derived contract tracked in markitect-tool
`docs/proposed-stale-derived-and-include-pinning.md`.
### 8. Relationship to ADR-0002
| ADR-0002 item | Under ADR-0003/0004 |
|---------------|---------------------|
| Markdown as content form | **Confirmed** |
| Assets beside content | **Confirmed** (tree layout; asset-store later) |
| Postgres not body SoR | **Confirmed** |
| One Forgejo repo per space as SoR | **Demoted** — optional remote/export; SoR is the **content tree + git** (ADR-0004) |
| Edit-in-Forgejo as primary write | **Optional** path; product PageOps may write tree/git directly |
## Consequences
### Positive
- User data remains files; agents and humans edit without the app DB.
- Stage-1 fields and card UI align with live Bubble vocabulary (Title, Abstractor, Visual).
- Fluency: top-down and bottom-up; live reuse; outline explode/implode without chunk ontology tax.
- Clear demotion of Forgejo-as-unique-SoR reduces lock-in to one forge layout.
### Negative / costs
- App must implement projection index invalidation when files change.
- Live includes need careful caching and security (path sandbox, cycles) via markitect.
- Lazy/AI generators need UX discipline so writers are not interrupted or overwritten.
- Explode/implode UX is non-trivial product work (connectivity, navigation).
### Neutral
- Identity remains ADR-0001; content authz consolidation is NetKingdom-side (ArchitectureBlueprint D3).
- Content plane storage mechanics and upgrades are **ADR-0004**.
## Non-goals
- Recreating Bubble marketplace, social graph, or monetization in this ADR.
- Choosing shard-wiki or kontextual as the *initial* plane (upgrade path only).
- Extracting a fleet asset-store repo (deferred).
- Full markitect derived-slot implementation timeline (owned by markitect-tool).
## References
- `docs/architecture/ArchitectureBlueprint.md` (D1D4, R1R12)
- `docs/architecture/transclusion-and-chunks.md`
- `docs/architecture/authoring-modes-and-lazy-projections.md`
- `docs/architecture/stale-derived-content.md`
- `docs/capability/stage-1-cutover.md`
- `docs/capability/existing-bubble-capability-map.md`
- ADR-0002 (provisional slice; partially superseded)
- markitect-tool: `docs/transform-compose-include.md`, `docs/explode-implode.md`,
`docs/proposed-stale-derived-and-include-pinning.md`