4.3 KiB
Domain model
Site (§12.1)
Site: id UUID, name TEXT, description TEXT NULL, canonical_url TEXT,
default_theme_id TEXT, created_at/updated_at TIMESTAMPTZ
Even though v1 proposes one site per installation, site_id scopes persisted data, leaving a migration path to multi-site without designing tenancy now.
Content entry (§12.2)
ContentEntry: id UUID, site_id UUID, content_type TEXT, slug TEXT, title TEXT,
status TEXT, excerpt TEXT NULL, published_at TIMESTAMPTZ NULL,
current_revision_id UUID NULL, created_at/updated_at, deleted_at NULL
Initial statuses: draft, published. Initial registered content type: core.post. Future content types are registry contributions, not if type === ... branches.
Content revision (§12.3)
ContentRevision: id UUID, content_id UUID, revision_number BIGINT,
document_version INTEGER, document JSONB, created_by UUID, created_at
Unique (content_id, revision_number).
Identifier policy (§12.4)
PostgreSQL 18 provides UUIDv7 generation — use UUIDv7 for major aggregates (globally unique opaque IDs with better temporal/index locality than UUIDv4).
Canonical content document (§13)
A post body is a versioned semantic block document:
{ "version": 1, "blocks": [ { "id": "019d...", "type": "core.paragraph", "version": 1, "props": { "text": "Hello world." } } ] }
Every block persists block instance ID, globally unique block type ID, block schema version, and validated properties. Theme CSS classes / concrete theme markup must not be stored as semantic content.
core.markdown block (§13.1, v1.1)
Markdown authoring was a locked product decision omitted from v1.0; it is restored as a block inside the block model. props.source is CommonMark + GFM (tables, strikethrough, task lists, autolinks) + footnotes; raw HTML disabled. Rules:
- Exactly one server-side Markdown renderer ending in one sanitiser with an explicit allowlist; the admin preview calls it (no second client-side renderer).
- A v0.1 post is a single
core.markdownblock by default — editor is a Markdown text editor with server-rendered preview. - When the block editor arrives, new content splits into finer blocks; existing
core.markdownblocks are never bulk-converted. - Export emits
props.sourceas.mdfiles with frontmatter — the portability guarantee.
Initial blocks (§14)
core.heading, core.paragraph, core.quote, core.code, core.divider, core.image (once media ships). v1.1: core.markdown joins the set as the primary authoring block.
Missing block behaviour (§14.1): public page still renders with a safe placeholder (no raw props, error logs include IDs); admin shows the missing dependency and preserves the original payload (no silent deletion).
Page composition (§15)
Home is an ordered list of versioned section instances:
{ "version": 1, "sections": [ { "id": "home-intro", "type": "core.site-intro", "version": 1, "enabled": true, "settings": {} }, { "id": "home-posts", "type": "core.post-list", "version": 1, "enabled": true, "settings": { "limit": 20 } } ] }
Initial composition UI supports only enable / disable / reorder / configure. Explicitly excluded: arbitrary pixel positioning, nested no-code canvas, user-authored raw HTML layouts, raw CSS editor.
Navigation (§28)
Persisted model: id, site_id, label, destination_type, destination, position, enabled, open_in_new_context, created_at, updated_at. Initial destination types: internal_route, external_url. Semantic <nav>, editable labels/order/visibility, external URL validation, disabled-extension destination yields an admin warning (never a silently broken link).
Column vs JSONB (§23.3)
Normal columns for data routinely constrained/joined/sorted/indexed/referenced/filtered. JSONB for versioned block props, settings, page section configuration, and sparse namespaced metadata.
Core table namespace (§23.1)
eppp_sites, eppp_site_settings, eppp_users, eppp_admin_sessions, eppp_content, eppp_content_revisions, eppp_page_compositions, eppp_navigation, eppp_extensions, eppp_extension_settings, eppp_extension_migrations, eppp_visitor_identities, eppp_visitor_preferences, eppp_media, eppp_jobs, eppp_job_runs, eppp_core_migrations. Extension tables use the ext_<name>_<table> prefix.