161 lines
5.2 KiB
Plaintext
161 lines
5.2 KiB
Plaintext
-- allium: 1
|
|
-- bDS Local Preview Server
|
|
-- Scope: core (Wave 4)
|
|
-- Distilled from: src/main/engine/PreviewServer.ts, PageRenderer.ts
|
|
|
|
use "./template.allium" as template
|
|
use "./generation.allium" as generation
|
|
|
|
entity PreviewServer {
|
|
host: String -- 127.0.0.1
|
|
port: Integer -- 4123
|
|
is_running: Boolean
|
|
}
|
|
|
|
config {
|
|
preview_host: String = "127.0.0.1"
|
|
preview_port: Integer = 4123
|
|
}
|
|
|
|
surface PreviewControlSurface {
|
|
facing _: PreviewOperator
|
|
|
|
provides:
|
|
StartPreviewRequested(project)
|
|
StopPreviewRequested(server)
|
|
OpenPreviewInBrowserRequested(project)
|
|
}
|
|
|
|
surface PreviewHttpSurface {
|
|
facing _: PreviewClient
|
|
|
|
provides:
|
|
PreviewRequest(path)
|
|
PreviewDraftRequest(path, post_id)
|
|
}
|
|
|
|
rule StartPreview {
|
|
when: StartPreviewRequested(project)
|
|
ensures: PreviewServer.created(
|
|
host: config.preview_host,
|
|
port: config.preview_port,
|
|
is_running: true
|
|
)
|
|
}
|
|
|
|
rule StopPreview {
|
|
when: StopPreviewRequested(server)
|
|
-- Graceful shutdown with inflight request tracking
|
|
ensures: server.is_running = false
|
|
}
|
|
|
|
rule OpenPreviewInBrowser {
|
|
when: OpenPreviewInBrowserRequested(project)
|
|
-- Starts or reuses the project's localhost preview server, then opens its
|
|
-- base URL in the external system browser when no post editor is active.
|
|
-- With an active post editor it opens that post's normal generated-site URL.
|
|
-- This is separate from the post editor's internal preview pane.
|
|
ensures: ExternalBrowserOpened(
|
|
url: active_post_generated_url or preview_base_url(project)
|
|
)
|
|
}
|
|
|
|
-- Route resolution
|
|
-- Preview renders all posts (published + draft) on-demand via Liquid templates.
|
|
-- Draft content comes from the DB; published content comes from its .md file.
|
|
-- See invariant PreviewDraftOverlay below.
|
|
|
|
rule ServePostPreview {
|
|
when: PreviewRequest(path)
|
|
requires: is_post_path(path)
|
|
-- path matches "/{yyyy}/{mm}/{dd}/{slug}"
|
|
-- Finds post by slug+date regardless of status (published or draft).
|
|
-- Draft bodies resolve from DB; published bodies resolve from .md files.
|
|
-- Renders via Liquid template with full PageRenderer context.
|
|
ensures: PreviewResponse(rendered_html)
|
|
}
|
|
|
|
rule ServeDraftPreview {
|
|
when: PreviewDraftRequest(path, post_id)
|
|
-- Editor preview uses the post's canonical generated route with draft and
|
|
-- post_id query markers, so links continue through the normal route tree.
|
|
-- Renders draft content from DB and published content from the filesystem.
|
|
ensures: PreviewResponse(rendered_html)
|
|
}
|
|
|
|
rule ServeArchivePreview {
|
|
when: PreviewRequest(path)
|
|
requires: is_archive_path(path)
|
|
-- Category, tag, date archives with pagination
|
|
-- Includes both published and draft posts in listings.
|
|
ensures: PreviewResponse(rendered_html)
|
|
}
|
|
|
|
rule ServeMediaFile {
|
|
when: PreviewRequest(path)
|
|
requires: is_media_path(path)
|
|
-- Path-traversal protection: validates path stays within media directory
|
|
ensures: PreviewResponse(media_file)
|
|
}
|
|
|
|
rule ServeAssets {
|
|
when: PreviewRequest(path)
|
|
requires: is_asset_path(path)
|
|
ensures: PreviewResponse(asset_file)
|
|
}
|
|
|
|
rule ServeGeneratedRuntimeData {
|
|
when: PreviewRequest(path)
|
|
requires: path = "/calendar.json" or is_feed_path(path)
|
|
or is_pagefind_path(path) or is_image_path(path)
|
|
-- Calendar and feed data use the preview post universe and normal list
|
|
-- visibility rules. Pagefind and image files are read from the same project
|
|
-- filesystem locations as the generated site.
|
|
ensures: PreviewResponse(runtime_file)
|
|
}
|
|
|
|
rule ServeLanguagePrefixedRoute {
|
|
when: PreviewRequest(path)
|
|
requires: is_language_prefixed(path)
|
|
-- Detects language prefix from supported languages
|
|
-- Renders with translation overlay for that language
|
|
ensures: PreviewResponse(translated_html)
|
|
}
|
|
|
|
invariant PreviewDraftOverlay {
|
|
-- Preview is the draft workspace: it shows what the blog *will* look like,
|
|
-- not what it currently looks like on the published site.
|
|
--
|
|
-- Post universe: all posts with status in {published, draft}.
|
|
-- Archived posts are excluded.
|
|
--
|
|
-- Content source (per post or translation):
|
|
-- 1. status=draft → DB content, falling back to its last-published file
|
|
-- 2. status=published → published .md file
|
|
-- 3. No DB body or file → empty string
|
|
--
|
|
-- This means:
|
|
-- - A purely draft post (never published) renders from DB content.
|
|
-- - A published-then-edited post renders from DB content (the draft edits).
|
|
-- - A published post renders from its .md file.
|
|
--
|
|
-- Contrast with generation (see generation.allium GenerationPublishedOnly):
|
|
-- Generation uses *only* published .md file content, never DB draft content,
|
|
-- and excludes posts that have never been published.
|
|
}
|
|
|
|
invariant ThemeSwitching {
|
|
-- Preview supports live theme/mode switching via query params
|
|
-- ?theme=amber&mode=dark etc.
|
|
-- Uses Pico CSS with configurable themes
|
|
}
|
|
|
|
invariant PreviewServerBinding {
|
|
for server in PreviewServers where server.is_running:
|
|
server.host = config.preview_host and server.port = config.preview_port
|
|
}
|
|
|
|
invariant LocalhostOnly {
|
|
-- Preview server binds to 127.0.0.1 only, never 0.0.0.0
|
|
}
|