feat: deckle v1 — Hugo CMS harness (complete rebuild) #32

Merged
dominik.polakovics merged 21 commits from afk/31 into main 2026-07-14 17:50:50 +02:00

Closes #31

Builds deckle v1 in full per the spec: a single Go binary that turns a plain Hugo site in a git repository into a safe CMS for a non-technical editor — SolidJS editing SPA, lossless canonical content engine, sidecar-YAML constraints, shipped element library + theme, git-backed save/publish, and one-command scaffolding.

Approach

The pre-reset tree (before PR #30) was restored as the baseline — it already covered the engine, Hugo pinning, supervisor, loopback server, sidecar loading, and tree browsing — then audited against this spec section by section (spec won every divergence: trailing-newline canonicalization, site-local .harness/ cache, harness.yaml title/languages, bool field vocabulary, typed placement, structured body policy) before the remaining milestones were built on top.

Build-order note (recorded per spec): milestones were kept serial on shared surfaces; the only reordering was pulling forward work on disjoint, new file surfaces (theme/library, media core, git package) while unrelated surfaces were in flight. No shared file was ever edited concurrently — the failure mode that killed the retired plan.

What shipped (by milestone)

  1. Engine — canonical parse/serialize with corpus + noncanonical goldens + fuzz (laws re-verified, exactly-one-trailing-newline made canonical); front-matter write half (byte-preserving YAML/TOML line editing, order-preserving JSON, oracle-verified); typed tree mutations by child-index path; fragment helpers with the zero-diff law pinned at engine and HTTP layers.
  2. Harness coreserve (loopback-only, supervised hugo server, pinned Hugo downloaded/checksummed into gitignored .harness/, PATH fallback with warning), harness.yaml (title, single language, pin), /api/site.
  3. Schemas — element sidecars (7-type field vocabulary, structure|content|inline groups, none|markdown|rte body policy with validated allowlist — h1 never — and allowedInline cross-checks, typed allowedIn/allowedAt placement), page-type sidecars with _root declaration, sidecar scaffolder CLI from Go template AST.
  4. Page tree + management — leaf bundles, collision-safe diacritic-folding slugs, rename/move with aliases (sections alias every descendant; published URLs never 404), deterministic weight-ladder reorder, draft toggle, delete with descendant counts, page settings from page-type fields; full tree UI with accessible menus, dialogs, DnD reorder.
  5. Element CRUD — grouped picker offering only placement-legal elements, DnD across/into containers, all three placement axes re-enforced server-side with files byte-untouched on violation; client mirrors the engine's merge/index semantics in a pure tested module.
  6. Library + theme + click-to-select — 18 elements (grid/column, spacer, text, text-media, image, gallery, hero, quote, video-embed with click-to-load privacy facade, accordion, page-teasers excluding drafts, developer-only html, contact-form, inline button/icon); modern mobile-first theme with tree-derived nav; ce/attrs markers only under hugo.IsServer (production hygiene test-pinned); origin-validated postMessage bridge; viewport toggle.
  7. RTE — self-owned Solid TipTap wrapper, per-field allowlists driving extensions/toolbar/shortcuts/paste (Word/GDocs mapped + stripped), no raw HTML ever (goldmark AST rejection server-side — the load-bearing defense given the theme's renderer.unsafe), links via URL or site tree, inline chips as atomic pills with sidecar popover forms and lossless round-trip.
  8. Media — magic-byte sniffing (evil.svg-as-.png rejected, extensions rewritten to truth), 15 MiB cap before decode, 2560px downscale + EXIF orientation + re-encode (originals never enter the repo), AVIF via pure-Go wazero libavif, PDFs byte-exact; bundle vs media/ library refs; single-file→bundle conversion on first upload; thumbnails outside the repo; delete warns with referencing pages, never blocks; alt required by default with sidecar opt-out.
  9. Git backbone — wip safety ref on every save (user's index/HEAD/branch untouched), single-step undo, per-page publish committing exactly the bundle after the non-negotiable pre-flight (main + publish set built with the pinned Hugo in throwaway dirs), local-wins-content/remote-wins-elsewhere rebase policy in a temp worktree, retry-safe push rejection, discard (never-published pages removed), published/modified/draft badges, honest degradation without git.
  10. Publish feedback — Forgejo/Gitea commit-status polling behind a forge abstraction (GitHub additive later), token from env/user-config only, backoff to terminal states, honest static fallback.
  11. harness init — embedded-by-construction scaffold (theme, library, sidecars, starter page, both CI variants with commented deploy steps); atomic never-overwrite refusal; init && serve verified working end-to-end.

Verification

  • go vet ./... clean; go test ./... green across all 16 packages (engine corpus/fuzz, placement matrices, page ops on temp site copies, git flows on real temp repos with a bare origin, forge fake servers, media sniffing/EXIF oracles, marker/bridge production hygiene).
  • 330 frontend tests across 18 files; tsc --noEmit clean; production build clean.
  • Engine fuzzers run clean (60s parse round-trip ~3.3M execs; 45s front-matter apply ~1M execs).
  • Live smoke: harness initserve → supervised Hugo running → preview HTML carrying data-ce-* markers; API surface exercised end-to-end.

Notes for review

  • The theme requires markup.goldmark.renderer.unsafe = true (nested shortcode output in rte bodies); the server's body validation rejecting raw HTML everywhere is the compensating control, documented in the theme README and code.
  • go.mod pins Hugo's parser library at v0.161.1 while the binary pin is 0.163.3 — independent concerns (lexer API vs build tool), flagged here for awareness.
  • Positional shortcode params are a documented read-error (named-only by design); org front matter is read-only.

🤖 Generated with Claude Code

https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3

Closes #31 Builds deckle v1 in full per the spec: a single Go binary that turns a plain Hugo site in a git repository into a safe CMS for a non-technical editor — SolidJS editing SPA, lossless canonical content engine, sidecar-YAML constraints, shipped element library + theme, git-backed save/publish, and one-command scaffolding. ## Approach The pre-reset tree (before PR #30) was restored as the baseline — it already covered the engine, Hugo pinning, supervisor, loopback server, sidecar loading, and tree browsing — then **audited against this spec section by section** (spec won every divergence: trailing-newline canonicalization, site-local `.harness/` cache, `harness.yaml` title/languages, `bool` field vocabulary, typed placement, structured body policy) before the remaining milestones were built on top. **Build-order note (recorded per spec):** milestones were kept serial on shared surfaces; the only reordering was pulling forward work on *disjoint, new file surfaces* (theme/library, media core, git package) while unrelated surfaces were in flight. No shared file was ever edited concurrently — the failure mode that killed the retired plan. ## What shipped (by milestone) 1. **Engine** — canonical parse/serialize with corpus + noncanonical goldens + fuzz (laws re-verified, exactly-one-trailing-newline made canonical); front-matter write half (byte-preserving YAML/TOML line editing, order-preserving JSON, oracle-verified); typed tree mutations by child-index path; fragment helpers with the zero-diff law pinned at engine and HTTP layers. 2. **Harness core** — `serve` (loopback-only, supervised `hugo server`, pinned Hugo downloaded/checksummed into gitignored `.harness/`, PATH fallback with warning), `harness.yaml` (title, single language, pin), `/api/site`. 3. **Schemas** — element sidecars (7-type field vocabulary, `structure|content|inline` groups, `none|markdown|rte` body policy with validated allowlist — h1 never — and `allowedInline` cross-checks, typed `allowedIn`/`allowedAt` placement), page-type sidecars with `_root` declaration, sidecar scaffolder CLI from Go template AST. 4. **Page tree + management** — leaf bundles, collision-safe diacritic-folding slugs, rename/move with aliases (sections alias every descendant; published URLs never 404), deterministic weight-ladder reorder, draft toggle, delete with descendant counts, page settings from page-type fields; full tree UI with accessible menus, dialogs, DnD reorder. 5. **Element CRUD** — grouped picker offering only placement-legal elements, DnD across/into containers, all three placement axes re-enforced server-side with files byte-untouched on violation; client mirrors the engine's merge/index semantics in a pure tested module. 6. **Library + theme + click-to-select** — 18 elements (grid/column, spacer, text, text-media, image, gallery, hero, quote, video-embed with click-to-load privacy facade, accordion, page-teasers excluding drafts, developer-only html, contact-form, inline button/icon); modern mobile-first theme with tree-derived nav; `ce/attrs` markers only under `hugo.IsServer` (production hygiene test-pinned); origin-validated postMessage bridge; viewport toggle. 7. **RTE** — self-owned Solid TipTap wrapper, per-field allowlists driving extensions/toolbar/shortcuts/paste (Word/GDocs mapped + stripped), no raw HTML ever (goldmark AST rejection server-side — the load-bearing defense given the theme's `renderer.unsafe`), links via URL or site tree, inline chips as atomic pills with sidecar popover forms and lossless round-trip. 8. **Media** — magic-byte sniffing (evil.svg-as-.png rejected, extensions rewritten to truth), 15 MiB cap before decode, 2560px downscale + EXIF orientation + re-encode (originals never enter the repo), AVIF via pure-Go wazero libavif, PDFs byte-exact; bundle vs `media/` library refs; single-file→bundle conversion on first upload; thumbnails outside the repo; delete warns with referencing pages, never blocks; alt required by default with sidecar opt-out. 9. **Git backbone** — wip safety ref on every save (user's index/HEAD/branch untouched), single-step undo, per-page publish committing exactly the bundle after the non-negotiable pre-flight (main + publish set built with the pinned Hugo in throwaway dirs), local-wins-content/remote-wins-elsewhere rebase policy in a temp worktree, retry-safe push rejection, discard (never-published pages removed), published/modified/draft badges, honest degradation without git. 10. **Publish feedback** — Forgejo/Gitea commit-status polling behind a forge abstraction (GitHub additive later), token from env/user-config only, backoff to terminal states, honest static fallback. 11. **`harness init`** — embedded-by-construction scaffold (theme, library, sidecars, starter page, both CI variants with commented deploy steps); atomic never-overwrite refusal; `init && serve` verified working end-to-end. ## Verification - `go vet ./...` clean; `go test ./...` green across all 16 packages (engine corpus/fuzz, placement matrices, page ops on temp site copies, git flows on real temp repos with a bare origin, forge fake servers, media sniffing/EXIF oracles, marker/bridge production hygiene). - 330 frontend tests across 18 files; `tsc --noEmit` clean; production build clean. - Engine fuzzers run clean (60s parse round-trip ~3.3M execs; 45s front-matter apply ~1M execs). - Live smoke: `harness init` → `serve` → supervised Hugo running → preview HTML carrying `data-ce-*` markers; API surface exercised end-to-end. ## Notes for review - The theme requires `markup.goldmark.renderer.unsafe = true` (nested shortcode output in rte bodies); the server's body validation rejecting raw HTML everywhere is the compensating control, documented in the theme README and code. - `go.mod` pins Hugo's *parser library* at v0.161.1 while the *binary* pin is 0.163.3 — independent concerns (lexer API vs build tool), flagged here for awareness. - Positional shortcode params are a documented read-error (named-only by design); org front matter is read-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Restores the tree from before the clean-slate reset (PR #30) as the
starting point for the deckle v1 rebuild (#31): content engine with
corpus/fuzz suite, harness CLI with Hugo version pinning, hugo server
supervisor, loopback server, element sidecar loading, page tree API,
and the SolidJS SPA shell. Subsequent commits audit this baseline
against the v1 spec and build the remaining milestones on top.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Document-final trailing-newline runs are non-semantic (gofmt-style):
Parse strips the run from the document-final text node and Serialize
always terminates a non-empty document with exactly one structural
newline. Programmatically built trees are normalized copy-on-write.
New noncanonical golden pairs and fuzz seeds cover inline-final,
text-final, and newline-run endings; 60s fuzz run clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Parses layouts/shortcodes/<name>.html with text/template/parse
(SkipFuncCheck), recovers named .Get params (incl. $.Get and chained
receivers), body policy from .Inner usage, and | default literals;
emits a draft editor/elements/<name>.yaml with every param typed text
and TODO markers. Recovered defaults are emitted as quoted strings so
drafts always load against the text type. Positional .Get calls get a
conversion note. Never overwrites an existing sidecar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Harness core: pinned Hugo now caches under the site-local .harness/
dir (gitignored) instead of the user cache; harness.yaml gains title
and languages (exactly one entry in v1, validated); the configured
language drives tree/page resolution end to end; new GET /api/site;
server.New takes an Options struct; loopback host refusal now has
test coverage. Wires the harness sidecar subcommand.

Element schemas: param vocabulary is exactly text/textarea/number/
bool/select/image/link (checkbox renamed, color/date dropped); body
policy is structured none|markdown|rte with a validated allow list
(h1 never valid) and allowedInline cross-schema checks; placement is
typed allowedIn/allowedAt with actionable errors; group validated
against structure|content|inline (default content).

SPA: sidecar load errors render in a banner; unknown elements read
'unknown element: <name>'; tree expansion and selection survive
browser reloads via sessionStorage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Loads editor/pagetypes/<name>.yaml (version 1, label, icon, children,
elements, fields) mirroring the element loader's error discipline.
fields reuse the element param vocabulary via the newly exported
element.ParseParams; tool-managed keys (type, draft, weight, aliases)
are rejected as fields. The reserved _root.yaml declares which types
may be created at the content root. Absent children/elements marshal
as null (unrestricted), explicit empty lists as [] (none permitted).
Tree nodes now surface the page's front-matter type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
FrontMatter.Apply(changes) sets/deletes top-level keys while
preserving every unmanaged byte: line-oriented editors for YAML and
TOML (comments, order, blank lines survive; replaced keys keep their
position; new keys append), order-preserving token-exact rewrite for
JSON, actionable refusal for org. Values are restricted to the types
the tool writes (string, bool, int64, float64, []string). Every edit
is verified by re-decoding with Hugo's own decoders against the
expected map — a layout the line model cannot safely edit is an
error, never a rewrite. Document.ApplyFrontMatter creates a YAML
block on front-matter-less pages. Golden fidelity fixtures plus a
45s fuzz run pin the contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Adds the canonical scaffold surface: 18 element shortcode templates
(grid/column, spacer, text, text-media, image, gallery(+item), hero,
quote, video-embed with click-to-load privacy facade, accordion(+item),
page-teasers excluding drafts, developer-only html, contact-form,
inline button and icon) with spec-conformant sidecars and page types;
a modern mobile-first theme (nav from the page tree with weight order,
hidden/menuTitle/footer flags, one keyboard-accessible dropdown level,
no JS framework) documented for forking; the ce/attrs partial emitting
data-ce-* markers only under hugo.IsServer with empirically verified
shortcode-ordinal paths; and the origin-validated postMessage bridge
(hello/ready handshake, select outbound, highlight inbound).

testdata/library-site demonstrates every element and doubles as the
per-element round-trip corpus; internal/theme pins sidecar loading,
canonical round-trips, production hygiene (zero markers, zero bridge,
zero placeholder leaks), and IsServer gating.

Note: the library site enables goldmark renderer.unsafe for nested
shortcode output in rte bodies; the no-raw-HTML guarantee moves to the
editor/save layer (RTE milestone) as recorded in the theme README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Forge-client abstraction (StatusClient, normalized states, run URL)
designed so GitHub is a later additive implementation. Forgejo/Gitea
combined-status API with token from DECKLE_FORGE_TOKEN or the
out-of-repo user config file, never from the repo, never logged.
Poller emits publishing/live/failed with backoff, stops permanently
on terminal states, and degrades once to an honest unknown phase on
API failure or a bounded no-CI window. Fake-server and fake-clock
tests throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
New internal/pageops package: leaf-bundle creation (index.md /
_index.md) with type-allowlist validation against page-type sidecars
(_root at the content root), collision-safe diacritic-folding slugs,
weight appended at the end of siblings; rename/move via one atomic
relocate mechanism appending old published URLs to aliases (sections
alias every descendant, existing aliases deduped) with rollback on
post-rename failure; deterministic 10-step weight ladder reorder;
draft toggle; recursive delete with descendant counts; page settings
read/save against the type's fields (title always editable) through
the byte-preserving front-matter Apply path. Eight new /api/page/*
endpoints follow the existing (path,lang) identity and error
conventions. Tested on temp site copies incl. byte-fidelity and
rollback cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
New internal/gitrepo package encapsulating every repo operation via
the git CLI: capability detection with graceful degradation (not a
repo / no main / no remote); whole-worktree wip snapshots to
refs/deckle/wip via a temp index (never touching the user's index,
HEAD, or branch; identical trees dedup); single-step undo support by
restoring path sets (incl. deletions) from a snapshot; publish that
fetches, reconciles a diverged main in a temporary worktree with the
local-wins-content / remote-wins-elsewhere rebase policy, builds a
detached commit of exactly the requested paths, pushes, and only then
advances refs/heads/main (push rejection is typed and retry-safe);
ExportTree materializing main + overlaid publish paths for the Hugo
pre-flight; per-path discard to main's state; Published/Modified/
Draft status derivation. 21 hermetic tests on real temp repos with a
bare origin, both rebase-policy directions, and retry-without-
duplicates coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Tree pane gains a toolbar and per-row accessible action menus: create
page/section dialogs offering only the parent-type-allowed page types
(_root at the content root) with live slug suggestion mirroring the
server's slugify (manual edit becomes a strict override); rename and
move dialogs with inline server errors and selection-follow; sibling
reorder via drag & drop plus keyboard Move up/down; draft toggle;
delete confirmation naming the page and its descendant count. The
center pane shows the page-type settings form (shared field-widget
machinery extracted from the element form, behavior unchanged) when
no element is selected. All mutations POST then refetch — no
optimistic UI. 61 new vitest cases (100 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
internal/content gains typed child-index-path mutations (NodeAt,
InsertChild, RemoveChild, MoveChild): full validation before any
tree change, adjacent-text merge on removal (Serialize refuses
adjacent text), toIndex interpreted against the post-removal list,
into-own-descendant rejection. New /api/element/{insert,delete,move}
endpoints build inserted shortcodes from schema defaults (block form
for body-capable elements, self-closing otherwise) and enforce all
three placement axes server-side — allowedIn incl. the page token,
allowedAt vs the page's effective type, and the page type's elements
allowlist — with the file byte-untouched on every failure. Unknown
elements stay movable and deletable but never insertable. /api/page
responses now carry the page's effective type for client-side picker
filtering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
New internal/media package: magic-byte-only type detection (jpeg,
png, webp, avif, pdf) with the 15MB cap enforced before any decode
and misleading extensions always rewritten to the sniffed type;
processing downscales to a 2560px max edge (CatmullRom, never
upscales), applies JPEG EXIF orientation 1-8 via a minimal IFD walk,
and re-encodes (JPEG q85 opaque / PNG alpha) so originals and their
metadata never enter the repo; PDFs pass through byte-exact. AVIF is
fully supported via pure-Go wazero-hosted libavif. Bundle refs are
bare filenames, library refs media/<path>, both round-trip-proven
through the canonical engine; sha256-cached 320px thumbnails live
outside the repo. The theme's new ce/img partial resolves both ref
shapes with intrinsic dimensions; the demo site exercises both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Every successful content mutation snapshots the working tree to the
wip ref (best-effort; saves succeed without git and the failure is
surfaced on /api/git/state). New endpoints: /api/page/undo restoring
a page's bundle from the previous snapshot through the canonical
path; /api/page/publish and /api/publish-all running the
non-negotiable pre-flight (ExportTree of main + exactly the publish
set, built with the pinned Hugo into throwaway dirs, 422 with the
Hugo output on failure, nothing committed); draft-link warning with
explicit force override; /api/page/discard distinguishing restored
vs removed (never-published) pages; /api/badges bulk-deriving
published/modified/draft per page; /api/publish/status serving the
forge poller's phase with the honest static message when no token or
remote exists. Push rejections map to a retryable 502; other git
failures carry contact-your-developer guidance. Tested against real
temp repos with a bare origin and a buildable Hugo fixture site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Insert affordances between top-level siblings and inside containers
open a grouped Structure/Content picker offering only elements the
position admits (allowedIn incl. the page token, allowedAt, page-type
allowlist, inline excluded), with empty states explaining why. Drag &
drop reorders among siblings at any depth and moves into/out of
containers (incl. empty ones) as exactly one mutation; invalid
targets show a no-drop affordance. The move math lives in a pure
module mirroring the engine's semantics — removal simulation with
adjacent-text merge, post-removal toIndex, merge-aware moved-node
paths — under 48 unit tests. Delete confirms with a subtree warning;
409/422 re-sync the tree and surface the server's message. 162 web
tests total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Multipart uploads are capped and gated in memory before any byte
touches the repo: sniff, process, then store — page uploads first
convert legacy single-file pages to bundles via the new byte-exact
pageops.EnsureBundle (no alias needed under default pretty URLs),
library uploads land under assets/media with slug-validated folders.
Listing endpoints serve library folders and page-bundle media (magic-
byte sniffed); thumbnails come from the gitignored .harness/thumbs
cache; a textual reference scan powers the delete warning without
ever blocking deletion. Traversal guards single-sourced in
media.ResolvePath. Uploads and deletes advance the wip snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
PreviewPane speaks the theme bridge protocol: hello on every iframe
load with the preview origin only, origin- and source-gated inbound
messages, ordinal-path conversion both ways (pure bridge.ts module),
click-to-select flowing through the one selection store with a
breadcrumb up to containers, hover/selection highlight cleared on
change, and a Desktop/Mobile viewport toggle. Publish UI covers the
draft-link warning with explicit override, the pre-flight failure
dialog with Hugo output and contact-your-developer guidance,
retryable push rejections, per-page and publish-all feedback chips
polling to Live/Build failed with a copyable run link and honest
static fallback. Discard copy distinguishes never-published removal;
Undo is honestly single-step; tree rows carry published/modified/
unpublished pills distinct from the front-matter draft badge, with
quiet degradation when git is unavailable. 210 web tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
harness init scaffolds a buildable site into an empty dir (or a
fresh repo holding only .git): full theme + element library + editor
sidecars embedded by construction from scaffold/ via go:embed,
templated hugo.toml (goldmark flag documented, baseURL to set before
deploy), harness.yaml with the title, single-language list and the
0.163.3 Hugo pin shared by both scaffolded CI variants (Forgejo and
GitHub: checkout, pinned Hugo, build; deploy steps present but
commented with rsync and pages examples), a canonical starter home
page, and .gitignore with .harness/. Refusal is atomic: any
conflicting path is reported and nothing is written. init && serve
needs zero manual steps — the test suite builds the initialized site
with real Hugo at zero warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Server: engine fragment helpers (ParseFragment/SerializeFragment,
byte-exact and guarded against front-matter lookalikes) power an
optional body field on save — parsed through the engine, whole-
children replacement, canonical serialize — and GET /api/element/
body. Validation is the enforcement point for both body policies:
raw HTML rejected everywhere via goldmark AST (the load-bearing
defense given the theme's goldmark unsafe flag), h1 rejected in
every syntax, rte constructs checked against the per-field
allowlist by name, chips checked against allowedInline, and a 409
clobber guard protects container elements' nested blocks. The
zero-diff law is pinned at the engine and HTTP layers.

Client: a self-owned Solid TipTap wrapper (no community packages);
the allowlist derives the loaded extensions so withheld shortcuts
and input rules simply don't exist; markdown-it/prosemirror-markdown
restricted to the same allowlist with pinned emission; inline chips
are atomic pills with sidecar-generated popover forms, exact
round-trip serialization, inert rendering for unknown or disallowed
inline shortcodes, and parse-time chip filtering on paste; Word/
Google-Docs paste maps to the allowlist and strips everything else
(h1 degrades to the first allowed level); links via URL or the site
tree storing pretty URLs. Bodies save only when dirty. 309 web
tests; full Go suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Full README rewrite covering the finished v1: promise and audiences,
quick start, per-subsystem how-it-works, configuration reference with
worked sidecar examples, scaffolding, development setup, and honest
v1 limitations. Repo CI now installs Hugo 0.163.3 — the version the
theme, tests, and init pin share. Alt text on the shipped image
elements is now required by default per the spec (sidecar opt-out
for decorative images documented); the init next-steps hint prints a
runnable serve command; stale placement not-yet-enforced comments
now point at the server-side enforcement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
feat(web): media picker — upload and library tabs, thumbnails, delete flow, alt grouping
Some checks failed
ci / build-and-test (pull_request) Failing after 8m33s
cbc022e72d
Image fields render a thumbnail widget with Choose/Clear opening the
two-tab media picker: page-bundle uploads by default with an explicit
upload-to-library toggle (folder select + validated new-folder),
inline 413/422 errors naming the sniffed type, and library browsing
with folders, breadcrumbs, a This-page group for bundle assets, and
generic PDF tiles. Legacy single-file pages transparently convert to
bundles on first upload — the tree refetches, selection follows the
new path, and dirty form state including an unsaved rich-text body
survives the remount. Deletion warns with the referencing pages and
never blocks or rewrites content. Image params group visually with
their alt field and its screen-reader hint. 330 web tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uRB6W7FXUjjVwtSSaLsD3
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!32
No description provided.