ZDDC/CLAUDE.md
ZDDC a02a26d3c2
All checks were successful
Build + deploy releases / build-and-deploy (push) Successful in 8s
feat: form-data system v0 (sixth tool + zddc-server endpoints)
Schema-driven form renderer plus zddc-server endpoints that turn any
<name>.form.yaml into a working data-collection form at <path>/<name>.form.html.
Submissions land in <path>/<name>/<YYYY-MM-DD>-<email-sanitized>.yaml,
ACL-gated by the existing .zddc cascade. The form posts back to its own URL;
the server strips ".html" and routes by what's underneath, so create and
update use the same client-side code path.

Form spec dialect: JSON Schema 2020-12 + RJSF-style ui:* hints, written in
YAML. Chosen for LLM authorability — it's the canonical structured-output
target for OpenAI/Anthropic, and the ui:* convention is the most-trained UI
hint vocabulary. Supported subset for v0: type (string/number/integer/boolean/
array/object), enum, min/max, minLength/maxLength, required, additionalProperties:
false, properties, items, format (date, email). Round-trip mode is form-as-truth:
submission YAML is regenerated each save, comments are not preserved (the v1
file-as-truth mode for hand-edited files like .zddc itself is deferred).

New components:
  * form/ — sixth single-file HTML tool, vanilla JS renderer (~760 LoC)
  * zddc/internal/jsonschema/ — focused JSON Schema validator covering only
    the v0 keyword subset. Match-implementation-cost-to-surface-used: a full
    library brings 70%+ surface we don't use; revisit when v1 adds $ref +
    oneOf + if/then/else.
  * zddc/internal/handler/formhandler.go — RecognizeFormRequest / ServeForm,
    capability-URL re-edit, atomic submission writes via the new
    zddc.WriteAtomic helper extracted from writer.go.
  * dispatch() in zddc-server/main.go now intercepts *.form.html and
    *.yaml.html before the static-file path; spec existence is the trigger.

Build pipeline: form joins ZDDC_RELEASE_TOOLS in lockstep, gets its own
embedded copy in handler/form.html (separate from the apps cascade —
the form renderer is fixed, not subject to per-folder version overrides).

Tests: 5 new Playwright specs (form-safety) + 14 new Go tests across the
validator and handler. All 172 Playwright tests + 10 Go packages green.
End-to-end manual verification: GET empty → POST 201 + capability URL →
GET re-edit (pre-filled) → POST update → 200, raw YAML browsable, ACL
deny → 403.

Docs: form/ section added to AGENTS.md and ARCHITECTURE.md. AGENTS.md
also documents the implementation-vs-dependency policy. CLAUDE.md repo-shape
list extended.

Deferred (v1+): .zddc editor migration onto this system, file-as-truth
lossless YAML round-trip, ui:show-when conditional visibility, oneOf/anyOf,
apps-cascade preview hook, cascade-fetched form definitions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:12:16 -05:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Authoritative docs — read these first

This repo already has two thorough agent-facing references. Always consult them before working — they cover details intentionally omitted here:

  • AGENTS.md — commands, build-system rules, per-tool parser quirks, testing gotchas, git/worktree workflow, release process, zddc-server notes
  • ARCHITECTURE.md — single-file HTML pattern rationale, JS module/state patterns, per-tool architecture, security model

If something in this CLAUDE.md conflicts with those, those win — and please update them rather than letting drift accumulate.

Repo shape

This is a monorepo of independent tools, not one application:

  • archive/, transmittal/, classifier/, mdedit/, landing/, form/ — six self-contained HTML tools, each compiled to a single inlined HTML file in its own dist/. Most output dist/tool.html; landing/ outputs dist/index.html (it's the project picker served at the root of zddc-server). The sixth tool, form/, is the schema-driven renderer for the form-data system (any <name>.form.yaml file in the tree becomes an editable form at <path>/<name>.form.html); see AGENTS.md "Form-data system" and ARCHITECTURE.md "Form Renderer".
  • zddc/ — Go HTTP server (separate sub-project; Go 1.24+). Serves ZDDC_ROOT/index.html at GET / as the landing page; Accept: application/json on / returns the ACL-filtered project list. Cross-compiled binaries are produced by ./build and live in dist/release-output/ (gitignored); ./deploy rsyncs them to /srv/zddc/releases/ on the deploy host (Caddy serves them at https://zddc.varasys.io/releases/). The helm/ charts in this repo build from source at deploy time.
  • shared/base.css plus shared JS modules (zddc.js, hash.js, zddc-filter.js, theme.js, help.js) included by every tool's build, and build-lib.sh (POSIX sh helpers sourced by every tool's build.sh AND by the top-level build for lockstep release helpers).
  • Two-repo + deploy-host model. Source code lives here (codeberg.org/VARASYS/ZDDC). Hand-edited website content lives in a separate repo (codeberg.org/VARASYS/ZDDC-website, typically cloned at ~/src/zddc-website/ — just index.html, reference.html, css/, js/, img/; no releases, no LFS). The live site at zddc.varasys.io is served from /srv/zddc/ on the deploy host: Caddy bind-mounts that path, and it's populated by ./deploy from this repo's dist/release-output/ plus ~/src/zddc-website/. Releases are NOT in any git history — they're reproducible from this repo's <tool>-vX.Y.Z tags by checking out the tag and running ./build release X.Y.Z. Per-version files (<tool>_v<X.Y.Z>.html) are immutable; partial-version pins (<tool>_v<X.Y>.html, <tool>_v<X>.html) and channel mirrors (<tool>_{stable,beta,alpha}.html) are symlinks; zddc-server has analogous zddc-server_v<X.Y.Z>_<platform> per-version binaries plus channel/partial-version symlinks plus zddc-server_<X>.html stub pages that fan out the four-platform download in one cell. Install model: local use is a download from /releases/. Server use is zddc-server, which has the current-stable build of all six tools baked in via //go:embed (compile-time default). Tools auto-served at folder-name-driven paths: archive everywhere, classifier in Incoming/Working/Staging subtrees, mdedit in Working subtrees, transmittal in Staging subtrees, landing only at root. Override via .zddc apps: cascade entry (channel/version/URL/path) — fetched once, cached at <ZDDC_ROOT>/_app/. Drop a real .html file at any path to override.
  • helm/ — example Helm charts for zddc-server (zddc-server-prod/, zddc-server-dev/). Both compile from source via init container. Operators copy values.yaml.example and customize. No secrets in repo.
  • tests/ — Playwright specs (Chromium only, requires File System Access API). tests/schema.spec.js validates transmittal.schema.json against canonical fixtures via ajv (only dev dep besides Playwright)

Most-used commands

# Source-side dev build only — assembles tool/dist/ + cross-compiles
# zddc-server. Does NOT touch dist/release-output/ or the live site.
./build

# Channel/release cuts — produce a complete release bundle in
# dist/release-output/ (gitignored). Cuts seed from the live site
# (/srv/zddc/releases/) so the bundle is a complete intended-live
# snapshot, not a sparse diff. Run ./deploy to publish.
./build alpha                        # cut alpha          (cascades nothing)
./build beta                         # cut beta           (cascades alpha → beta)
./build release                      # cut stable, coordinated next version
                                     # (cascades alpha + beta → new stable; tags all seven tools)
./build release X.Y.Z                # cut stable at explicit version
./build help                         # usage

# Deploy — atomic-ish rsync of the build output + content repo to
# /srv/zddc/, where Caddy serves it. The build does NOT auto-deploy.
./deploy                             # full sync: content + releases
./deploy --content                   # only ~/src/zddc-website/ → /srv/zddc/
./deploy --releases                  # only dist/release-output/ → /srv/zddc/releases/

sh tool/build.sh                     # iterate on one HTML tool's dist/
sh tool/build.sh --release [...]     # single-tool release (rare; prefer the lockstep ./build)
./freshen-channel <tool> <channel>   # rebuild one tool's alpha/beta from its current stable tag

npm test                             # all Playwright specs (build first!)
npx playwright test <tool>           # one spec
./dev-server start  # stop           # cache-busting HTTP on :8000

# zddc/ Go server (sub-project)
(cd zddc && go test ./...)           # unit tests (Go 1.24+)

No lint/typecheck/format commands exist for the HTML tools — vanilla JS + POSIX sh by design.

Things that bite if you forget

  • dist/ is gitignored. tool/dist/<tool>.html is the canonical built artifact for testing and as the source for --release writes. dist/release-output/ is the local-only release bundle written by ./build alpha|beta|release. Never hand-edit a dist/ file.
  • Build vs deploy are separate verbs. ./build and ./build alpha|beta|release produce artifacts under dist/release-output/. Nothing escapes the source tree until the operator runs ./deploy, which rsyncs into /srv/zddc/ (Caddy's bind-mount). This decouples local iteration from live state.
  • Channel/release cuts seed from live state. Before running per-tool promote, ./build alpha|beta|release clears dist/release-output/ and copies /srv/zddc/releases/ into it (preserving symlinks). The cut then mutates the channels being cut on top. Result: dist/release-output/ is always a complete intended-live snapshot, the verifier sees a complete world, and ./deploy --releases (rsync --delete-after) replaces live state cleanly.
  • Lockstep releases. Every release cut bumps all seven artifacts (6 HTML tools + zddc-server) to the same version, even if a tool didn't change. The coordinated next-stable target is max(latest tag across all tools) + 1. Per-tool independent versions are no longer the norm — ./build release is the canonical path. Workflow: alpha = active dev, beta = ready for general testing, stable = ready to ship.
  • Release artifact layout (in dist/release-output/, mirrored to /srv/zddc/releases/). HTML tools: per-version <tool>_v<X.Y.Z>.html (real immutable files) + partial-version pins (<tool>_v<X.Y>.html, _v<X>.html) + channel mirrors (<tool>_{stable,beta,alpha}.html) — all symlinks except per-version. zddc-server: zddc-server_v<X.Y.Z>_<platform> per-version binaries (raw bytes, no LFS), _v<X.Y>_<platform> / _v<X>_<platform> / _<channel>_<platform> symlinks, plus zddc-server_<X>.html stub pages that surface the four platform downloads in one matrix-cell link. Same cascade rule for both: stable cut → beta + alpha both reset to stable; beta cut → alpha cascades to beta.
  • No tags for alpha/beta. Channel URLs are stable URLs by design — appending counter tags would defeat the purpose. The on-page label encodes <date> · <sha> for traceability. Stable cuts get clean <tool>-vX.Y.Z tags for every tool (six tags per cut, all sharing the same X.Y.Z).
  • Pre-release semver in the on-page label. Plain dev builds and --release alpha|beta cuts embed vX.Y.Z-{alpha,beta} in {{BUILD_LABEL}} where X.Y.Z is the next-stable target. Plain dev adds a full timestamp + -dirty marker; --release alpha|beta is date-only.
  • Channel-link verifier. Every ./build alpha|beta|release ends with a check that every <tool>_{stable,beta,alpha}.html (and zddc-server's per-platform binary mirrors + stub pages) resolves. Because cuts seed from live state, the verifier always sees a complete world; missing-link errors mean a real problem, not a sparse-bundle artifact.
  • ./build (no arg) is a source-side dev build. Assembles tool/dist/ + cross-compiled binaries; does NOT touch dist/release-output/ or the live site. Use it to iterate without affecting anything. To produce a deployable bundle, run ./build alpha|beta|release. To publish, run ./deploy. Nothing is pushed to Codeberg automatically.
  • Always build before running tests — Playwright opens dist/tool.html via file://.
  • </ in JS string/template literals breaks inline <script> embedding. shared/build-lib.sh provides escape_js_close_tags; every tool's build.sh runs JS through it before inlining.
  • All ZDDC parsing/formatting/hashing goes through window.zddc (from shared/zddc.js + shared/hash.js + shared/zddc-filter.js). API: parseFilename, parseFolder, parseRevision, formatFilename, formatFolder, compareRevisions, isValidStatus, splitExtension, joinExtension, crypto.{sha256Hex, sha256String, sha256File, bytesToHex}, filter.{parse, matches}. File objects across tools use trackingNumber (string) and extension (string, no leading dot — use zddc.joinExtension(name, ext) to build a filename). Add edge cases to tests/zddc.spec.js, not per-tool tests.
  • Two globals only: window.app (per-tool app state + modules) and window.zddc (shared library). No others — anything that crosses tool boundaries goes through one of these.
  • Worktrees live at ~/src/zddc-<branch>. Check git worktree list before starting a feature branch; never git checkout/switch inside a worktree another agent might be using.
  • Build scripts are POSIX sh with set -eu, not bash. concat_files takes positional args only.