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.
7.7 KiB
| id | title | status | date | deciders | related | workplans | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ADR-0003 | Page-centric markdown SoR, authoring, and live derived content | accepted | 2026-08-13 | bernd |
|
|
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 Bubble’s 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
- Content SoR is markdown files (frontmatter + body + backmatter) and assets under a directory tree versioned in git (see ADR-0004).
- 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.
- identity linkage (
- 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)
- Authors write pages fluently; structure is headings and optional named regions.
- Reuse uses address → live transclude → promote (markitect include/selector/region), not a mandatory
Chunkentity graph. - Bubble
custom.chunkmigration prefers sections/regions or connected pages with includes — not permanent recreation of chunk tables. - 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:
- If absent and display needs them → generate lazily → persist as normal field values (as if user-provided for export).
- Per-field regenerate and lock (lock blocks auto-fill and regenerate until unlock).
- 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.
- 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
- coulomb depends on markitect-tool for parse, frontmatter, query/extract, live include, explode/implode, and (when implemented) derived-slot stale/refresh APIs.
- coulomb does not “run on” markitect as a product platform; markitect-main is not a runtime foundation.
- 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(D1–D4, R1–R12)docs/architecture/transclusion-and-chunks.mddocs/architecture/authoring-modes-and-lazy-projections.mddocs/architecture/stale-derived-content.mddocs/capability/stage-1-cutover.mddocs/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