Skip to content
This repository was archived by the owner on May 9, 2026. It is now read-only.

docs: v1.x editor end-state UX (prototypes + refactor plan)#150

Open
slavasolutions wants to merge 46 commits into
mainfrom
feat/v1-editor-end-state-doc
Open

docs: v1.x editor end-state UX (prototypes + refactor plan)#150
slavasolutions wants to merge 46 commits into
mainfrom
feat/v1-editor-end-state-doc

Conversation

@slavasolutions

Copy link
Copy Markdown
Contributor

Summary

End-state UX brief for the v1 admin editor — the prototypes the v1.0 stamp won't quite hit, scoped as v1.x roadmap. Filed because the live test admin (sandbox @ :1999) surfaced the duplicate-preview-chrome regression (#149) plus a few related rough edges; the doc consolidates the target for the polish arc.

What's in

  • Pain inventory from today's live test (5 items, screenshots referenced)
  • ASCII mockups for: item editor article mode, page editor form mode, sections surface, preview overlay, mobile compact view
  • Refactor plan — 7 PRs ordered by operator-impact-per-effort, starting with topbar consolidation + metarail group collapse

What v1.0.0 ships anyway

Functionally complete (sections / tokens / blocks / mode-gating / hosted SSO / migration runner). The chrome rough edges from this doc are visible but not blocking.

Coordination

Doc-only; doesn't conflict with the v1.0.0 stamp PR (#148). Targets `main` so it's discoverable post-stamp.

🤖 Generated with Claude Code

slavasolutions and others added 30 commits May 6, 2026 14:44
Lock seven new ADRs (and one supersession) from the 2026-05-06
grilling session that reshapes v1 around an opt-in site mode.

- 0021 Headless / Site mode split — per-project flag, UUID + manifest
  block ordering (reframes ADR-0007 from definition to storage strategy)
- 0022 Record schema-version persistence — every record stamps version,
  declarative + imperative migrations chain at read
- 0023 Editor surface registry — closed core (form/article/sections/canvas)
  with internal contract; future-opens to third parties non-breaking
- 0024 Site mode renderer = library + scaffold template
  (supersedes ADR-0008)
- 0025 Per-bucket theme tokens — design-system foundation, schemas
  reference via optionsFromTokens, store token name (not resolved value)
- 0026 Preview tier contract — T0/T1/T2 named tiers (T1 mostly built;
  site mode auto-emits markers; T2 reserved)
- 0027 Markdown as first-class widget alongside richtext —
  two prose widgets, two storages
- 0028 Block library publishing rhythm — strict semver,
  package-shipped migrations

CONTEXT.md updated with the new vocabulary (Mode, Site, Block,
Block-schema, Theme tokens, Preview tier, Schema version, Richtext
extensions; Frontend / Editor mode / Widget extended). STATE.md
records the architectural pivot and links the new ADRs.

ADR-0007 reframed (storage strategy, not block definition).
ADR-0008 superseded by ADR-0024.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.3.0 in package.json never reached npm (broken publish workflow,
fixed by #21). Rather than catch-up publish, accumulate the queued
changesets + open feature PRs and ship as v0.4.0 in one wave.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 9 queued packages now on npm. Documents the path that got us
there (six commits + per-package TP config) so future debug saves
that walk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two compounding causes:

1. Cached libSQL singleton in lib/db.ts opened lazily when a route is
   imported was never closed by tests. Added closeDb() export.
2. Windows occasionally needs a retry window on rm even after close().
   Added safeRm() helper in test/teardown.ts (5 retries, 50ms backoff).

Wired into pages-reindex, schemas/[key], preview/[token], backup, restore
test suites. All 389 admin tests pass on Linux. Windows clone should
clear the EBUSY blocking Eng B's full test runs.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/0028) (#57)

Squash of 9 commits — contract trail preserved here per the PR body's
"do not squash" intent (forced by repo policy: only squash-merge is
allowed). The per-commit narrative below maps to the changeset entries
under .changeset/ that ship with this PR.

1. f4c6f4d feat(spec): add clear.config.json mode field (ADR-0021)
2. 4e0147c feat(spec): add schemaVersion + version per ADR-0022
3. dabb8e5 feat(spec): add sections + canvas editor modes (ADR-0021/ADR-0023)
4. eb50062 feat(spec): add markdown as a first-class widget (ADR-0027)
5. 2a4c137 feat(spec): per-field extensions allowlist for richtext + markdown
6. f2bdd41 feat(spec): per-field optionsFromTokens for select widgets (ADR-0025)
7. ed0bea0 feat(spec): block-schema dialect + block-instance + blockStorage hint
8. 96a0a3b feat(spec): per-bucket tokens shape + Migration types (ADRs 0025/0022)
9. b6c6294 chore(spec): self-review polish — drop dup config re-export,
   symmetric tokens re-exports

Phase 3 of the v1.0 release plan. Extends @clearcms/spec with the
contract surface site mode and schema-version persistence depend on.

Eng B track per clearcms/planning/decisions/2026-05-06-v1-architecture.md.

Track tests: 219/219 (spec) — typecheck clean, build green.
Self-review: APPROVE — no blockers, three LOW-severity nits addressed in commit 9.

Closes the Phase 3 spec foundation milestone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squash of 6 commits — Eng B Phase 5/6 storage track. Trail preserved
here per the no-squash intent (forced by repo policy: only squash-merge
allowed). Per-commit narrative maps to .changeset/ entries.

1. 4956c11 feat(storage): write-time schemaVersion stamp (ADR-0022)
2. 40612df feat(storage): named-global writers — identity / nav / tokens
3. e8db616 feat(storage): writeItem + writePage with status routing
4. 971102c feat(storage): writeBlock + resolveBlockStorage + writePageData stamp
5. f31fe83 feat(storage): bucket-path helpers
6. b696176 feat(admin): clear-admin backfill-schema-versions CLI surface (stub)

Adds the load-bearing schema-version write primitives + every named
writer the v1 plan asks for, all routing through `writeStampedJson`:

- `stampSchemaVersion(record, schemaVersion?)` — pure precedence
  chain (explicit arg → record's existing valid semver →
  `DEFAULT_SCHEMA_VERSION`).
- `writeStampedJson(adapter, key, record, schemaVersion?)` — atomic
  temp+rename with stamp.
- Named globals: `writeIdentity` / `writeNav` / `writeTokens` /
  `writeGlobal` (generic dispatch on closed-set name).
- Records: `writeItem` (status-routed via `itemPath()`), `writePage`
  (`content/pages/<slug>/index.json`).
- `writeBlock` for `blockStorage: "files"` per-instance file path.
- `writePageData` augmented to stamp on object payloads.
- `resolveBlockStorage(collectionSchema, collectionSlug)` strategy
  resolver (default `"files"` for `pages`, `"inline"` elsewhere).
- Path helpers `themeBlocksDir` / `pageBlocksDir` / `pageBlockPath`.
- `clear-admin backfill-schema-versions` CLI surface (stub; full
  bucket walk lands in Phase 6 closeout).

Track tests: spec 219/219 + storage 89/89 = 308 ✅. Full implementation
of bucket walk pending Phase 6.

Stacked on PR #57; auto-retarget didn't fire — manually retargeted to
release/v1.0 + rebased --onto to drop the now-merged spec commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squash of 2 commits — Stream-Migrate. Trail preserved here per
no-squash intent (forced by repo policy).

1. 89441bc feat(spec): migration runner + library discovery (ADR-0022, ADR-0028)
2. ebf44a1 feat(admin): clear-admin migrate-records stub (ADR-0022)

`@clearcms/spec/migrate` runtime: `migrate(record, from, to, migrations)`,
`buildMigrationChain` (greedy shortest-path), `applyOp` for each
declarative op kind (rename / default / remove / tiptap-to-markdown /
markdown-to-tiptap), `discoverMigrations` walking both
`<bucket>/migrations/` and `node_modules/@clearcms/blocks-*/migrations/`
per ADR-0028.

Markdown ↔ TipTap conversions are best-effort with `_migration_warnings`
annotation per ADR-0027. In-package serializers (no `marked` / `remark`
deps).

`apps/admin/src/cli/migrate-records.ts` — argv shape (`--collection
<slug>` | `--all`, `--dry-run`) locked, full bucket walk + persist
deferred to Phase 6 closeout.

Track tests: spec 247/247 (+25 for migration runner). Stacked on PR #57;
manually retargeted + rebased --onto release/v1.0 to drop the spec
commits already squashed via #57.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squash of 2 commits — Stream-Renderer scaffold.

1. f06d7f8 feat(renderer): library scaffold (ADR-0024)
2. 43c2d70 chore(renderer): clarify FOLLOWUP(#57) marker for spec-types swap

`@clearcms/renderer` library shape (NOT an Astro integration). Types
+ dispatch only — no real blocks yet.

Exports: `renderBlocks(blocks, registry)` (pure dispatch),
`UnknownBlock` (no-op fallback per ADR-0021), `usePreview()` (SSR-safe
`?clear-preview=` detection + `markField` helper for ADR-0026 T1
markers). Framework-neutral — no React/Astro/Vue dep.

`RendererBlockInstance` is a local mirror with a `FOLLOWUP(#57)` marker
for the swap to `@clearcms/spec/blocks` once #57 lands. Now that #57
is merged, the swap is queued — see `grep -r 'FOLLOWUP(#57)' packages/`.

9 tests cover renderBlocks happy path with known + unknown blocks,
`UnknownBlock` fallback, `usePreview` URL detection, `markField`
output shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1/0022/0025) (#75)

Stream-Doctor — 4 new `clear-admin doctor` checks for the v1
site-mode + ADR-0022 schema-version contract. Each is a pure async
function returning `DoctorIssue[]`; all four return zero issues on a
healthy v0/v1 bucket.

- `checkModeConsistency` (ADR-0021) — `clear.config.json`'s `mode`
  vs the bucket's site-mode artifacts. Headless + `theme/blocks/`
  files present → warning. Site + no `theme/blocks/` → info.
- `checkDanglingBlockTypes` (ADR-0021) — page `data.json` block
  manifests reference unknown block types → warning each.
- `checkStaleSchemaVersions` (ADR-0022) — record `schemaVersion`
  vs the schema's current `version`, per-collection counts.
  Suggests `clear-admin migrate-records` for stale collections.
- `checkDanglingTokenRefs` (ADR-0025) — block-instance fields with
  `widget: "select" + optionsFromTokens: <category>` reference token
  names absent from `theme/tokens.json[<category>]`.

Wired into `doctor.ts`'s main run sequence so `clear-admin doctor`
surfaces the new checks alongside the 10 existing ones.

Tests: 18/18 across 4 new test files. Pure fs fixtures (`mkdtemp` +
JSON file writes); no SQLite DB harness — Windows EBUSY pre-existing
issue (#56) doesn't apply.

Local `assumeSchemaVersion` shim in `doctor-stale-schema-versions.ts`
that should be swapped to `@clearcms/spec`'s now that #57 has merged.
Tracked as a follow-up cleanup PR per the lead's review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squash of 2 commits — Stream-Docs CONTENT-PROTOCOL v1 rewrite.

1. (initial commit) docs(protocol): CONTENT-PROTOCOL.md v1 rewrite (site mode)
2. (review-nits commit) docs(protocol): address review nits — ADR-0024 scope note + SDK schemaVersion default

Rewrites `docs/CONTENT-PROTOCOL.md` to cover v1 site mode. Archives
the v0 file to `docs/CONTENT-PROTOCOL-v0.md` via `git mv` (history
preserved).

14 sections covering: Overview (mode split) | Bucket layout | Schema
versioning + migrations | Block storage strategy | Theme tokens |
Markdown / richtext widgets | Item document schema | Page document
schema | Block library publishing | REST API | Site mode example |
Headless mode unchanged | Doctor + backfill commands | Summary TOC.

Cross-references ADRs 0021 / 0022 / 0024 / 0025 / 0027 / 0028 inline.

Review nits addressed in commit 2:
- §1 scope note clarifying ADR-0024 (renderer architecture) lives
  outside this protocol doc by design — bucket-level contract only.
- §3 SDK-author note on the absent-`schemaVersion` = `"1.0.0"` rule;
  recommends `assumeSchemaVersion` over `.default()` Zod parse so the
  doctor's "untracked" check stays meaningful.

1015 lines. Status line: v1, descriptive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establishes the rigid task list + turn-based coordination for the rest
of v1. Both engineers (S1 = lead/Eng A, S2 = Eng B) read these files at
session start and follow them strictly.

PLAN.md is the ordered task queue from now to v1.0.0. Each task is
tagged [S1] or [S2] with prereqs noted. Phase 3 already complete (all
6 PRs merged today). Phase 4-8 still queued.

BATON.md is a single-line file (S1 or S2) marking whose turn it is to
merge to release/v1.0. Initial value: S2 — Eng B has #102/#103 to
process before lead picks up Phase 4.

Hard rules in PLAN.md:
- One in-flight merge at a time per session.
- Strict alternation enforced by BATON.
- No direct push to release/v1.0 except the two-line BATON+PLAN flip
  commit immediately after each merge.
- Surface ownership zones to prevent file-level conflicts.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#102)

Reflects the v1 wave merged on release/v1.0 (PRs #57/#72/#73/#74/#75/#76).

Updates header date + tagline; new "v0.5 conceptual milestone" section listing each merged PR with SHA + scope; bucket protocol section flips to v1 layout; "in flight" section points at next gates (#52, blocks-marketing); ADR status block updated per ADR.

closes T-3.7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-3.9 (lead's #101 migrate-bucket-to-r2 merge) is blocked: the
@clearcms/spec barrel re-exports migrate/run.ts (node:fs/promises +
node:url + node:path), so Vite drags it into both client and server
chunks of admin's astro build. Build fails with __vite-browser-external,
or smoke tests fail with ERR_MODULE_NOT_FOUND at runtime. Confirmed
with a clean lockfile + green typecheck + 426/426 tests + an
astro.config external mark — the issue is fundamentally in the spec
package's exports shape.

Spec is S2 surface (per PLAN ownership zones). Inserting T-3.8.5 [S2]
as a hot unblocker: split @clearcms/spec/migrate so the bucket-walking
runner is a server-only sub-export. Detail in PLAN.md.

Flipping BATON to S2 to enable the fix. T-3.9 stays open and unticked
— S1 will retry it after S2's spec fix lands and lead's #101 rebases
clean. T-3.10 / T-3.11 / T-3.12 reordered for cleaner Phase 3 wrap-up
(pulled #103 blocks-marketing forward as T-3.11 since it's already in
flight).

This commit modifies only BATON.md + PLAN.md per protocol.
…rowing (#106)

Bundled fix unblocking the entire v1 PR queue.

Per-commit trail:
- e9e9b4b fix(spec): split migrate runner — Node-only fs in /migrate/runtime sub-export
- 07b3aa8 fix(deps): regenerate pnpm-lock.yaml after spec exports map change
- 6370200 fix(spec,core,storage,design): publish hygiene — dist-only tarballs + spec/migrate split
- eae5978 fix(admin): narrow editor mode + add markdown widget label (cherry-picked from #109 per S1's Path A)

What this fixes:
1. @clearcms/spec migrate split — run.ts now pure (no node:* imports). Node-only discovery lives in new runtime.ts at @clearcms/spec/migrate/runtime (node condition only). Drops src from files whitelist.
2. Publish hygiene — @clearcms/core, @clearcms/storage, @clearcms/design flipped from raw-TS to dist-only with tsconfig.build.json + conditional exports map.
3. Admin enum narrowing — PageItemEditor + SchemaPreview narrow editorModeFor() via requiresSiteMode guard; PageContentTypeFields uses 'as const'; AddFieldModal adds 'markdown' to WIDGETS + WIDGET_DESCRIPTIONS. Per-callsite narrow until Phase 4 editor surface registry (#52).

Changesets:
- @clearcms/spec: minor (export shape change)
- @clearcms/core: minor
- @clearcms/storage: minor
- @clearcms/design: minor
- @clearcms/admin: patch

Verification:
- pnpm --filter @clearcms/admin typecheck → 0 errors
- pnpm --filter @clearcms/admin build → green (no node:* in browser bundle)
- pnpm pack across the four packages confirms dist-only tarballs

Closes #105
Closes #107
Closes #108
Closes T-3.8.5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(admin): clear-admin migrate-bucket-to-r2 (closes #14)

One-shot fs → R2 data migration. Reads ~/clear/registry.json, creates the
target bucket via the platform R2 token from R2_* env vars, PUTs every
file under bucket/ at the same key, atomically rewrites registry.json
with bucketProvider:'r2', and renames the local bucket dir to
bucket.archived-<ISO>/ so the operator can verify parity before deleting.

Implements ADR-0017 v1 happy path (steps 1, 2, 3, 5, 6, 7, 8). Status
flipped from proposed to accepted.

Deferred (called out in the changeset):
  - Step 4: per-project scoped R2 token mint per ADR-0016.
  - Resume-on-failure via .migration-progress.json (ADR-0017 Risks).
  - Streaming PUTs for large media (v1 reads each file fully into memory;
    fine for text-shaped sites, fails on multi-GB media libraries).

Env contract: reads R2_* (the platform-token shape from
~/.r2-platform.env), distinct from the per-project CLEAR_R2_* the admin
reads at boot. CreateBucket requires admin scope across all buckets, so
the platform token is the right input here; ADR-0016 will replace this
with a per-project token mint.

Tests: vitest fixture suite injects a recording StorageOps double; covers
happy path, content-type map, --bucket-name override, already-on-R2
short-circuit, missing-slug + missing-source-bucket fail-fast, env
validation, and that other registry entries are preserved during rewrite.
8/8 pass.

Dogfooded against ~/clear/clear-marketing/ (37 files, 172K, fs → R2
bucket "clear-marketing"). Post-migration verified: archive dir present,
original bucket renamed, registry shape correct, and astro build with
CLEAR_BACKEND=r2 produces the same 12 pages as the fs baseline (only
diff is the homepage's local _demo/ walker, which is site-code-specific
and unrelated to the migration).

Discovered during testing: doctor.test.ts > "PASS for a present bucket
dir" has a tight 5s timeout on a sqlite3 execSync loop that occasionally
trips when running alongside other parallel test files. Pre-existing
fragility (passes 397/397 with --no-file-parallelism); the new test
file just adds enough load to expose it. Out of scope for this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): remove obsolete vite externals for spec/migrate

Pre-T-3.8.5 astro.config.ts marked @clearcms/spec/migrate as a
ssr.external + rollup external, which baked the BUILD-TIME absolute
path of /home/runner/packages/spec/dist/migrate/run.js into the
items chunk. That path is correct on the dev machine but doesn't
exist on the CI runner (which builds from /home/runner/work/clear/
clear/...), so the smoke test failed with ERR_MODULE_NOT_FOUND.

T-3.8.5 (PR #106) fixed the underlying problem structurally by
splitting @clearcms/spec/migrate into a browser-safe barrel and a
Node-only @clearcms/spec/migrate/runtime subpath. The vite externals
in admin became obsolete and harmful — same posture as release/v1.0
HEAD, which has none.

This commit removes the externals so admin's build matches HEAD's
configuration. Local build passes; CI smoke test should now resolve
the chunk imports through Node's normal package-exports resolution
instead of an absolute file path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First v1 block library shipping with create-clear scaffolds and renderer support.

Per-commit trail:
- 10542dc feat(blocks-marketing): scaffold + Hero (ADRs 0024/0025/0028)
- 3ffca3c feat(blocks-marketing): Features + CTA + FAQ + Footer + Contact (5 blocks)
- 43154d4 feat(blocks-marketing): Testimonials + Pricing + Logos + RichText (final 4 blocks)

10 blocks: Hero, Features, CTA, FAQ, Footer, Contact, Testimonials, Pricing, Logos, RichText.
Each block ships an Astro component + Zod schema with the v1 widget dialect (ADR-0027).
Theme tokens (ADR-0025) referenced via optionsFromTokens binding.
Schema versioning per ADR-0022; lockstep publishing per ADR-0028.

Changeset: @clearcms/blocks-marketing minor.

Closes T-3.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements ADR-0023's closed-core surface registry. The schema's
`editor:` keyword resolves through `surfaceFor(schema)` to one of
four surfaces — form, article, sections, canvas — each implementing
the EditorSurface interface (validate + Component). PageItemEditor
and PagePageEditor dispatch through the registry instead of
branching on a narrowed 'form' | 'article' value.

- apps/admin/src/surfaces/types.ts — EditorSurface, SurfaceProps,
  SurfaceContext, SurfaceValidationResult, SiteContext + ok()/failed()
  helpers.
- apps/admin/src/surfaces/{form,article}/index.tsx — delegate to
  SchemaForm for behavior parity. Article validator wraps
  validateArticleSchema (exactly one richtext required).
- apps/admin/src/surfaces/{sections,canvas}/index.tsx — not-implemented
  placeholders. Sections validator refuses outside site mode + when
  no block schemas exist. Canvas validator always refuses (post-v1).
- apps/admin/src/surfaces/index.ts — surfaceFor() dispatcher + the
  closed registry object. Type re-exports for consumers.
- PageItemEditor + PagePageEditor: surface dispatch + inline
  SurfaceValidationFailed render when validate() returns ok: false.

Tests: 10 cases covering dispatch defaults, named-value resolution,
per-surface validators (article richtext-count, sections site-mode
+ block-schema gates, canvas reserved-for-post-v1, form always-ok),
and registry shape integrity.

Per ADR-0023 the registry is internal-only — no public registration
API in v1. Opening it to third-party surfaces is a future ADR keyed
off concrete adopter demand.

Closes T-4.1, T-4.2, T-4.3, T-4.4, T-4.5, T-4.6, T-4.7. Closes #52.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(admin): plumb project mode at boot (T-5A.1, ADR-0021)

Adds the runtime resolution path for ADR-0021's mode flag. Site-mode
surfaces (tokens UI, sections editor, designer extension, blocks
library) consume getProjectMode() / isSiteMode() in T-5B/C/D PRs to
gate sidebar entries and route guards.

- project-config.ts: mode: ProjectMode now resolved from env CLEAR_MODE
  > file.mode > headless. Provenance tracked in sources.mode. Unknown
  values fall back to default rather than throwing — boot must succeed
  even with a slightly off config so the operator can fix the file
  from the running admin.
- project-mode.ts: cached getProjectMode() + isSiteMode() helpers for
  runtime hot paths. Cache resets only via _resetProjectModeCache()
  (tests only).

Existing v0.x installs default to headless and see no behavior change.
The site-mode-only sidebar entries + route guards land in T-5B.1
(/settings/site/tokens) and T-5D.1 (sections route exposure).

Tests: 6 helper cases + 4 resolver cases covering default / file /
env / env-override / unknown-value fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): address #120 review findings — provenance + test cwd hygiene

Self-review pass on T-5A.1 found two in-scope fixes:

1. project-config.ts mode resolution: provenance was reported based on
   which input was *populated*, not which input *resolved*. A bogus env
   like CLEAR_MODE=composer would record sources.mode='env' while
   actually using the default — misleading in doctor output. Fixed to
   use a validity-aware predicate so sources.mode reports the source of
   the *resolved* value.

2. project-mode.test.ts: process.chdir leaked across tests in the same
   worker, and chdir'ing to /tmp picked up stray clear.config.json
   files on dev machines. Fixed by capturing originalCwd in beforeEach
   + restoring in afterEach, and using mkdtemp for the default-case
   tests instead of /tmp.

Follow-up filed separately for the third finding (wire
doctor-mode-consistency.ts to consume the new resolver + warn on typo'd
mode values).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
slavasolutions and others added 16 commits May 7, 2026 05:41
…ag + integration tests (#121)

T-5F.1 — site-mode scaffold pre-wired with @clearcms/blocks-marketing v1.

Per-commit trail:
- 4d44e3a feat(create): template-site-marketing scaffold + --theme=marketing flag
- 48f5974 test(create): integration tests for create-clear CLI (--theme, replacements, --force)

What's in:
- packages/create-clear/template-site-marketing/ — full scaffold (clear.config mode=site, theme tokens, theme/blocks/*.schema.json for all 10 marketing blocks, sample home page with 3 pre-populated blocks: Hero + Features + CTA)
- packages/create-clear/bin/create-clear.js — THEMES map, --theme <name> and --theme=<name> forms, validation
- packages/create-clear/test/create-clear.test.js — 26 integration tests subprocess-invoking the CLI: --help, default theme, --theme=marketing scaffold structure, replacements, --force, error paths
- vitest infra wired (matches workspace pin 4.1.5); test/ path keeps tests out of the published tarball (verified via pnpm pack: 48 entries, zero *.test.*)

Per-callsite footprint, independence-checked: zero S1-territory paths.

ADRs: 0021 (mode split), 0022 (schemaVersion persistence), 0024 (block library), 0025 (theme tokens), 0027 (widget dialect), 0028 (lockstep publishing).

Changeset: @clearcms/create minor.

Closes T-5F.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(admin): theme tokens UI (T-5B.1, site-mode only)

Implements PLAN T-5B.1: site-mode editor for theme/tokens.json.

- /settings/site/tokens route (gated by isSiteMode() — headless installs
  302 to /settings).
- TokensPanel: 3 groups per the PLAN spec — colors, typography,
  "space/radius/shadow/motion." Inputs are direct CSS strings; ColorPicker
  pairs a native swatch with a text input so operators can fast-pick or
  type oklch() values.
- LivePreview: sandboxed button + heading + card rendering with the live
  state, transitioning per the motion.medium token.
- adminTokens.save action: isSiteMode-gated; writes via writeTokens from
  @clearcms/storage. Permissive zod schema so adopters can add custom
  groups (e.g., breakpoints, z-index) without a code change.

DEFAULT_TOKENS mirrors the marketing-template scaffold so a fresh
admin lands on a sensible empty state. 5/5 tests on the shape.

Closes T-5B.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): deep-merge tokens loaded from bucket so partial files keep defaults

Self-review found a real bug: tokens.astro spread { ...DEFAULT_TOKENS, ...fromBucket }
replaces the entire group when bucket has e.g. just color.primary, dropping
all other default keys the editor + LivePreview expect. Replaced with a
mergeTokens() helper that deep-merges per group + adds 5 tests covering
the merge semantics (partial group preserve, new key add, new group
verbatim, scalar override, null/undefined no-op).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er-bucket + Hero v1→v2 (#136)

T-6.1 — replaces the migration runner stub from PR #76 with the full v1 impl.

Per-commit trail:
- ba3b9dd feat(migrate): full migration runner — declarative + imperative + greedy-at-read + lazy-on-save (T-6.1 draft)

Spec (pure runner + Node-only fs discovery):
- run.ts: applyChain, inferTargetVersion + BlockTypeRegistryEntry type
- runtime.ts: discoverPerBucketMigrations (ADR-0015), loadBlockMigrations (ADR-0028)

Storage greedy-at-read bridge:
- New migrate-bridge.ts (Node-only sub-path) — readMigratedItem/Page/PageData/Block
- Lazy-on-save: caller's call. Storage stays adapter-agnostic.

Admin CLIs (shared, S2-owned per task body):
- migrate-records.ts full impl (--bucket --type --collection --dry-run --verbose)
- backfill-schema-versions.ts full impl

Blocks-marketing sample (Hero v1→v2):
- subheadline → subhead rename + secondaryCta{Label,Href}
- migrations/Hero/1.0.0-to-2.0.0.json declarative

Changesets:
- @clearcms/spec: minor
- @clearcms/storage: minor
- @clearcms/admin: minor
- @clearcms/blocks-marketing: major (Hero schema bump)

Verification: spec 264/264 pass, storage 99/99, blocks-marketing 23/23, new CLI 32/32, admin astro check clean.

ADRs: 0015, 0022, 0024, 0028.

Closes T-6.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-5A.1 follow-up. The mode-consistency check now uses
loadProjectConfig().mode as the resolver — single source of truth
across env / file / default. Critical addition: when the resolver
fell back to default because the raw value didn't validate, the
check WARNs with the operator-supplied bad value. Without this,
mode='sote' silently boots as headless and the operator can't tell
their typo wasn't honored.

Tests: 9 cases (was 5). Added 4: file.mode typo, env.CLEAR_MODE
typo, typo prevails over headless+blocks WARN, valid env value
doesn't trip the typo branch.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(admin): block-schema designer extension (T-5C.1, ADR-0024)

Site-mode-only — extends the visual SchemaDesigner to read+write
theme/blocks/<Name>.schema.json files alongside the existing
collection + layout schemas.

- lib/schemas.ts: 'block' added to SchemaKind; b:<PascalName> key
  encoding with isValidBlockName guard (PascalCase only, ADR-0024);
  listSchemas now scans theme/blocks/*.schema.json; readSchemaByKey
  + writeSchemaByKey convert between BlockSchema (on-disk shape:
  { name, version, fields }) and ClearSchema (designer shape:
  { type: 'object', properties }) so the existing SchemaDesigner
  works without modification. Block meta (name/version/title/
  description/deprecated) round-trips via a clear:block field the
  designer ignores; final-form BlockSchemaSchema validation on
  write.

- SchemaList.tsx: third "Block schemas" section in the left rail,
  shown only when at least one block exists.

- settingsSections.ts: Settings sub-nav adds "Schemas" + "Block
  schemas" entries (gated by schemaEditor).

- pages/settings/blocks/index.astro: site-mode list (302 to /settings
  in headless, /schemas in site).

- pages/settings/blocks/[name].astro: site-mode designer wrapper
  (302 to /schemas/b:<name>; rejects non-PascalCase names with 400).

Tests: 18 cases (was 10). New: block-key encoding (PascalCase
required, path-traversal rejected, empty rejected), dialect
conversion round-trip (fields ↔ properties + meta + deprecated +
default-version fallback).

Closes T-5C.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): address #122-style review on T-5C.1 — site-mode gate + cache + spec parity

Self-review pass on the block-designer extension found 3 in-PR fixes:

1. invalidateCachesForKey: block kind was falling through to the layout-
   cache invalidation branch, calling invalidateLayoutSchema() with a
   block name. Harmless today (no block-schema cache exists yet) but
   wrong shape. Added explicit block branch (no-op + comment).

2. Site-mode leak on /schemas + /api/admin/schemas: the existing schema
   designer routes had no isSiteMode() gate, so a headless operator who
   bookmarked /schemas/b:Hero would land on a working block-designer
   surface. Now: /schemas/[key] returns 404 when key is `b:` and not
   site-mode; /schemas + /schemas/[key] filter blocks out of the rail;
   POST /api/admin/schemas/[key] returns 403 on block writes in headless.

3. PascalCase regex was stricter than @clearcms/spec's BlockNameSchema
   (which allows `[A-Za-z][A-Za-z0-9-]{0,59}` — hyphens + lowercase
   leading). Mismatch meant on-disk hyphenated block schemas would be
   reachable on disk but invisible in the designer. Reconciled by
   matching spec's regex (max 60, leading alpha, alnum+dash). Tests
   updated.

Tests: 19 (was 10 → 18 after T-5C.1 → 19 with the spec-parity case).

Filing follow-ups for: writeSchemaByKey block-path coverage, listSchemas
blocks-present coverage, redirect-route tests, title==name drop edge
case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(admin): sections surface MVP (T-5D.1-4, ADR-0021/0024)

Replaces the Phase 4 SectionsPlaceholder with a real composition UX.
Site-mode admins with at least one block schema can now author a
sections page end-to-end: add block instances, edit each block's
fields via per-type SchemaForm, reorder via up/down buttons, delete.

Files:
- surfaces/sections/types.ts — SectionInstance, SectionsManifest,
  toManifest defensive coercion, newSectionId crypto-random helper.
- surfaces/sections/SectionCard.tsx — header (type label + reorder +
  delete) + body (per-block fields via SchemaForm with block-schema
  converted to ClearSchema). UnknownBlockType renders inline error.
- surfaces/sections/AddSectionPicker.tsx — minimal select + add UI;
  empty state when no block types exist.
- surfaces/sections/SectionsSurface.tsx — manifest stack + dispatcher.
- surfaces/sections/index.tsx — validator now returns ok() when
  siteContext + at least one block schema. Component = SectionsSurface.

Storage strategy: v1 ships inline-in-pageData. Per-instance block
files (writeBlock + blockInstancePath) land as v1.x follow-up. dnd-
kit reorder, visual block picker, click-to-focus from preview also
deferred to v1.x — operator can author end-to-end with the MVP.

Tests: 11 surface-registry cases (added "sections passes when site-
mode + blocks" branch) + 7 manifest helper cases.

Closes T-5D.1 / T-5D.2 / T-5D.3 (basic) / T-5D.4. Defers T-5D.5/6/7/8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): wire siteContext to surface dispatchers (closes block-merge from review)

Self-review on T-5D found a hard blocker: PageItemEditor and
PagePageEditor passed `{}` as the surface validation context, so
sections surface's validator (which requires `ctx.siteContext`)
returned failed() unconditionally — surface never actually mounted.

Fix:
- New `lib/site-context.ts` with `loadSiteContext()` + cache. Reads
  every theme/blocks/*.schema.json and theme/tokens.json into a
  SiteContext shape. Returns null in headless mode.
- PageItemEditor + PagePageEditor accept `siteContext?: SiteContext |
  null` prop, thread it through to surface.validate() + Component.
- .astro callers (item editor + page editor) pass `await
  loadSiteContext()` as the prop.

Plus the memo fix from the same review:
- SectionsSurface uses useMemo for types + schemaForType conversion
  (was rebuilt every render). Removed useCallback that was no-op
  due to props-deps capturing every-render onChange.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): keep storage server-only by converting block schemas in site-context

CI failure on PR #142: SectionsSurface imported blockSchemaToClearSchema
from lib/schemas.ts, which transitively imports @clearcms/storage's
readJson/writeJson — pulled the FS adapter (node:fs/promises, node:path)
into the client bundle and exploded with "join is not exported by
__vite-browser-external".

Fix: move the BlockSchema → ClearSchema dialect conversion into
loadSiteContext() (server-side .astro path). The SiteContext that
arrives at the editor island already carries ClearSchema-shaped block
schemas, so SectionsSurface no longer needs to import any conversion
helper or anything else from lib/schemas.ts. Bundle stays clean.

Local `astro build` now succeeds; CI should match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T-7.1 — ADR status sweep + block-library author guide + CONTENT-PROTOCOL v1 polish.

ADR sweep:
- 0007 reframed by 0021 (page block storage shifted to v1 dialect)
- 0008 superseded by 0024 (Workers renderer dropped for self-hosted Astro)
- 0021–0028 carry Implementation status (2026-05-07) sections with PR references where shipped

ADR README index:
- 0007 row updated to reframed
- 0019 noted as skipped/reserved
- 0021–0028 annotated with v1 impl status
- New v1 ADR set one-line summaries

New file: docs/guides/block-library-authoring.md (~325 lines)
- Required exports + widget vocabulary (ADR-0027)
- optionsFromTokens binding (ADR-0025)
- Versioning + migrations (ADR-0022)
- Publishing cadence (ADR-0028)
- Worked Pricing block example
- Reference: @clearcms/blocks-marketing

CONTENT-PROTOCOL polish:
- "What's new in v1" section
- Cross-link to author guide
- Renderer name fix: clear-site → @clearcms/renderer

Changeset: empty frontmatter — root docs, no package version bumps.

Closes T-7.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(T-7.3+T-7.4 partial) (#146)

Pre-stages T-7.3 (README v1 rewrite) + T-7.4 (STATE.md refresh) docs in
parallel with Eng B's T-7.1 docs sweep (separate paths — they touch
docs/adr/ + docs/CONTENT-PROTOCOL.md + docs/guides/, this PR touches
README.md + docs/migration-v0.2-to-v1.0.md + STATE.md).

- README.md — adds prominent "Two modes (v1, ADR-0021)" section near
  the top: explains headless vs site mode + points at the migration
  guide. Existing scaffolder/quick-start sections unchanged.
- docs/migration-v0.2-to-v1.0.md (new) — TL;DR upgrade for headless
  installs (just bump deps + run doctor); spec/storage table of
  changes with backwards-compat notes; site-mode opt-in walkthrough
  (config flag → scaffold → sections layout → renderer); breaking-
  change list (rare; only matters for internal-import consumers);
  rollback notes.
- STATE.md — refreshed package versions to match npm reality (#110)
  + reflect v1 phases shipped: admin 0.4.2 with v1.0.0 queued, create
  0.4.0 with --theme=marketing, blocks-marketing 0.1.0 new, renderer
  0.1.0 scaffold. Header line updated to v1.0 release prep status.

T-7.3 (full): full clear-admin command reference + integration guides
(Astro/React/SDK) + REST docs deferred to v1.0.x — covered by issue
referencing #94.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the editor end-state target the v1.0 stamp won't quite hit:
single topbar (no duplicate preview chrome), preview overlay instead of
side-panel-with-its-own-toolbar, sections outline rail, collapsed-by-
default metarail groups, mobile bottom-sheet pattern.

ASCII mockups for: item editor (article mode), page editor (form mode),
sections surface, preview overlay, mobile compact view.

Refactor plan: 7 PRs ordered by operator-impact-per-effort, starting
with topbar consolidation (#149) + metarail group collapse.

This is the brief for v1.0.x → v1.1.0 editor work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant