ZDDC/CLAUDE.md
ZDDC 470a34a690 docs: drop alpha/beta channels + partial-version pins from repo docs
Match the build/build-lib + apps.go simplification in bdd1460. Each
tool now has one canonical URL (<tool>.html, symlink → current stable)
and a set of immutable per-version files (<tool>_v<X.Y.Z>.html). Beta
cuts are internal-only (SHA snapshot for the BMC dev chart); no public
beta or alpha channels exist anymore.

Touched:
- CLAUDE.md "Repo shape" + "Things that bite" — drop channel mirrors
  and partial-version pins from the artifact-layout bullet, rewrite
  the seed-from-live bullet, drop "channel-link verifier" bullet,
  rewrite build-label bullet for the dev/beta/stable shape.
- AGENTS.md "Commands" + "Releasing — lockstep stable + beta snapshot"
  (renamed from "lockstep, channels, layout") + "Release discipline"
  (renamed from "Channel discipline"). "Freshen helper" section
  deleted entirely. Artifact-layout table simplified.
- ARCHITECTURE.md Build System + Channels (renamed "Release verbs")
  + Install distribution model. Artifact-layout block + label table
  + spec syntax in the `.zddc apps:` cascade — all rewritten.
- zddc/README.md release-tagging + apps-resolver spec syntax +
  signing-pipeline section.

The May 2026 simplification is now self-documenting — references to it
appear where readers might wonder why the older shape is gone.

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

93 lines
15 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commits and pushes
- **Commit freely** — make commits as appropriate for the work being performed. Each commit should be a coherent, reviewable unit (no WIP/checkpoint noise). The default rule "never commit without explicit ask" does NOT apply in this repo.
- **Push only when explicitly told** — `git push` requires a fresh request from the user every time. Approval to commit does not carry forward to push, and approval to push once does not carry forward to a later push.
- **No squashing on push** — keep granular history. Each commit should already be meaningful (per the rule above), so squashing erases useful detail rather than removing noise. Multi-commit branches with a clean history are preferred over force-pushed squash-merges.
## 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/`, `landing/`, `form/`, `tables/`, `browse/` — seven 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`). `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`); `tables/` is its read/aggregate counterpart, rendering a directory of YAML rows as a sortable table; `browse/` is the file-tree navigator and **also hosts the in-place markdown editor** (`browse/js/preview-markdown.js` — Toast UI Editor + YAML front-matter pane + on-demand server-side MD→DOCX/HTML/PDF download buttons). A dedicated `mdedit/` tool used to live alongside these but has been retired. See AGENTS.md "Form-data system" / "Tables system" and ARCHITECTURE.md "Form Renderer".
- `zddc/` — Go HTTP server (separate sub-project; Go 1.24+). Two deployment shapes from the same binary: (1) **master** — owns a file tree under `ZDDC_ROOT`, applies `.zddc` ACL cascades, serves files / app HTML / archive listings. Two auth paths on master: `Authorization: Bearer <token>` validated against self-issued tokens at `<ZDDC_ROOT>/.zddc.d/tokens/<sha256-hex>` for CLI/scripted callers, or `X-Auth-Request-Email` injected by an upstream proxy for browser sessions. Self-service token UI at `/.tokens` + JSON API at `/.api/tokens`. (2) **client** — when `--upstream <url>` is set, the binary becomes a downstream proxy/cache/mirror (`zddc/internal/cache/`); master-side machinery is bypassed and `--root` becomes the cache directory. Three sub-modes via `--mode proxy|cache|mirror` (mirror is phase 3). Cache layout is a normal ZDDC root, so the cache dir can be served as a plain master if you unset `--upstream`. Marker file `.zddc-upstream` records provenance. `--no-auth` skips ACL enforcement entirely on this instance (distinct from `--insecure` which only relaxes the no-root-`.zddc` startup check); `--skip-tls-verify` is a separate flag for self-signed upstream certs. 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/` — CSS (`base.css`, `fonts.css` + base64-inlined woff2 under `fonts/`, `nav.css`, `logo.css`, `toast.css`) plus shared JS modules (`zddc.js`, `hash.js`, `zddc-filter.js`, `zddc-source.js`, `zip-source.js`, `theme.js`, `toast.js`, `nav.js`, `logo.js`, `help.js`, `preview-lib.js`) and vendored libs (`vendor/`: jszip, xlsx, utif, docx-preview, toastui-editor) — each tool's `build.sh` concatenates the subset it needs. Also `build-lib.sh` (POSIX sh helpers sourced by every tool's `build.sh` AND by the top-level `build` for lockstep release helpers). See AGENTS.md "Shared modules" for the full inventory.
- **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 seven HTML tools baked in via `//go:embed` (compile-time default). **Which tool a directory URL serves is driven by the `.zddc` cascade, not hardcoded folder names** — a baked-in `defaults.zddc.yaml` (dump it: `zddc-server show-defaults`) declares, via a recursive `paths:` tree, per-folder `default_tool` (served at `<dir>``archive` under `archive/`, `transmittal` under `staging/`, `browse` under `working/`+`reviewing/` (hosts the markdown editor), `classifier` under `incoming/`, `tables` at `archive/<party>/mdl`, `landing` at root), `dir_tool` (served at `<dir>/`; defaults to `browse`), `available_tools`, plus the canonical-folder behaviour keys (`auto_own`, `worm:`, `virtual`, `drop_target`); operators override at any level. A `.zip` file is also a navigable directory (`GET …/Foo.zip/` → member listing; `…/Foo.zip/m.pdf` → that member); `GET /dir/?zip=1` streams an ACL-filtered zip of a subtree. Override the *tool source* via a `.zddc apps:` entry (channel/version/URL/path) — fetched once, cached at `<ZDDC_ROOT>/_app/`; or drop a real `.html` at any path. See AGENTS.md "URL handling"/"Install model" and ARCHITECTURE.md "Canonical folders, URL routing & the `.zddc` cascade".
- `helm/` — example Helm charts for zddc-server. Three flavors: `zddc-server-prod/` (production master), `zddc-server-dev/` (development master with OverlayFS isolation), `zddc-server-cache/` (downstream client running in proxy/cache/mirror mode against an upstream master, with bearer token from a Kubernetes Secret). All compile from source via init container. Operators copy `values.yaml.example` and customize. No secrets in repo — the cache chart references a separately-created Secret for the bearer token.
- `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
```bash
# Source-side dev build only — assembles tool/dist/ + cross-compiles
# zddc-server. Does NOT touch dist/release-output/ or the live site.
./build
# ./build beta — internal SHA snapshot for the BMC dev chart pipeline.
# Regenerates zddc/internal/apps/embedded/* and makes a
# `chore(embedded): cut v<X.Y.Z>-beta` commit. NO public artifacts.
# The chart's appVersion pins to "<X.Y.Z>-beta-<sha>"; its Dockerfile
# parses the suffix and fetches that SHA from git.
./build beta
#
# ./build release — coordinated stable cut. Regenerates embedded/,
# makes a release commit, tags all 8 artifacts, writes per-tool
# <tool>_v<X.Y.Z>.html + <tool>.html canonical symlink, and zddc-server
# per-platform binaries + canonical symlinks into dist/release-output/.
# Bundle seeded from /srv/zddc/releases/ so prior immutable per-version
# artifacts survive.
./build release # coordinated next-stable version
./build release X.Y.Z # explicit stable 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 [X.Y.Z] # single-tool stable cut (rare; prefer ./build release)
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 the source for `./build release` writes. `dist/release-output/` is the local-only release bundle. Never hand-edit a `dist/` file.
- **Build vs deploy are separate verbs.** `./build` and `./build 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.
- **Stable cuts seed from live state.** Before running per-tool promote, `./build release` clears `dist/release-output/` and copies only the per-version immutable files (`<tool>_v<X.Y.Z>.html`, `zddc-server_v<X.Y.Z>_<plat>`) plus their `.sig` sidecars from `/srv/zddc/releases/`. The cut writes this version's per-version files + refreshes the canonical `<tool>.html` / `zddc-server_<plat>` symlinks on top. `./deploy --releases` (rsync `--delete-after`) cleanses any stale files in the live tree that this cut didn't include.
- **Lockstep releases.** Every release cut bumps all 8 artifacts (7 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 gone — `./build release` is the canonical path. Stable cuts atomically (1) regenerate `zddc/internal/apps/embedded/` with stable-labeled bytes, (2) make a `release: vX.Y.Z lockstep` commit, (3) tag all 8 artifacts at that commit. Tags always point at a clean release commit. (Anchor fix May 2026; see git log around the v0.0.9 re-anchor.)
- **Bake-in invariant.** What zddc-server's binary embeds via `//go:embed`: prod images (chart's Dockerfile.prod fetches the latest stable tag) ship that cut's bytes. Dev images (chart's Dockerfile fetches `appVersion`, which is either a stable tag or a `<X.Y.Z>-beta-<sha>` snapshot SHA) ship the bytes that ref carries. Plain `./build` (no arg) leaves `embedded/` untouched — local dev iteration uses `tool/dist/<tool>.html` opened directly, not the baked binary copy.
- **Release artifact layout** (in `dist/release-output/`, mirrored to `/srv/zddc/releases/`):
- HTML tools: `<tool>_v<X.Y.Z>.html` (real immutable file) + `<tool>.html` (symlink → current stable's per-version file). Each carries a sibling `.sig` (real for per-version, symlink for canonical).
- zddc-server: `zddc-server_v<X.Y.Z>_<platform>` (real immutable binary, no LFS) + `zddc-server_<platform>` (symlink → current stable's per-version binary). Same `.sig` pairing. Plus a single `zddc-server.html` stub page that surfaces the four-platform downloads of the current stable.
- No channel mirrors (`_alpha`, `_beta`, `_stable`), no partial-version pins (`_v<X.Y>`, `_v<X>`). Dropped in the May 2026 simplification.
- **On-page build label.** Plain dev builds: `v<X.Y.Z>-dev · <full-ts> · <sha>[-dirty]` (red), where X.Y.Z is the next-stable target. `./build beta`: `v<X.Y.Z>-beta · <full-ts> · <sha>` (red) — only seen on the dev chart's compiled binary. Stable cuts: clean `v<X.Y.Z>`.
- **`./build` (no arg) is a source-side dev build.** Assembles `tool/dist/` + cross-compiled binaries; does NOT touch `dist/release-output/`, embedded files, or the live site. Use it to iterate without affecting anything. `./build beta` adds the embedded regen + chore commit (BMC dev chart consumes the SHA via appVersion). `./build release` produces the deployable bundle. `./deploy` publishes. 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.
- **Admin elevation is sudo-style.** Admins behave as normal users by default; opting into admin powers is per-request and gated by the `zddc-elevate=1` cookie (Max-Age=1800, set by the header toggle in every tool). Server-side: `zddc.Principal{Email, Elevated}` is built once per request by `handler.ACLMiddleware` and threaded into `IsAdmin`/`IsSubtreeAdmin`/`CanEditZddc` — the compiler enforces the gate at every admin call site (no easy "forgot to check elevation" mistake). Bearer-token requests are implicitly elevated since CLI clients can't toggle a cookie; browser sessions elevate only when the user clicks the header checkbox. `/.profile/access` exposes `can_elevate` (elevation-independent "does this email have admin authority anywhere?") so the header toggle can decide whether to render itself for an un-elevated admin. The access-log captures the `elevated` flag per request for forensics.
- **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.