Replace the build-counter version scheme (every alpha push monotonically
bumps the patch number, producing immutable :0.0.X tags that look
indistinguishable from stable releases) with proper semver pre-release
suffixes. Stable owns clean vX.Y.Z; alpha and beta carry
vX.Y.Z-{alpha,beta}[.N] indicating the next-stable target.
The next-stable target is the patch-bump of the latest clean
<prefix>-vX.Y.Z tag. Counter N is per-channel (alpha and beta count
separately) and resets when a new stable advances next-patch. Used
only for zddc-server image tags, where every release is git-tagged;
HTML tools omit the counter since alpha/beta cuts there don't tag.
release-image.sh:
- New CLI: sh release-image.sh [alpha|beta|stable] [<version>].
- Default channel alpha. Version arg only valid (and only optional)
for stable.
- Auto-derives the version via next_prerelease for alpha/beta, and
patch-bump for unspecified stable.
- Now creates the git tag itself (the auto-derived version is no
longer something the operator can predict in advance), but does
not push — operator finishes with `git push --tags`.
shared/build-lib.sh:
- Add next_prerelease(channel, tag_prefix) helper.
- compute_build_label embeds v<next-stable>-{alpha,beta} in the
on-page label for plain and --release alpha|beta builds.
- Plain builds: v<next-stable>-alpha · <ts> · <sha>[-dirty]
--release alpha: v<next-stable>-alpha · <date> · <sha>
--release beta: v<next-stable>-beta · <date> · <sha>
--release [<version>]: v<X.Y.Z> (clean stable, unchanged shape).
Pre-release semver ordering (vX.Y.Z-alpha.1 < vX.Y.Z-alpha.2 <
vX.Y.Z-beta.1 < vX.Y.Z) is honored by registry tag sorting,
git tag --sort=-v:refname, sort -V, npm, cargo — so consumers can
pin or compare versions without surprises.
Existing zddc-server-v0.0.{3..7} git tags and registry tags are
audit history; not rewritten. Going forward, alpha/beta cuts produce
v0.0.8-{alpha,beta}.N format, and clean v0.0.8 is reserved for a
deliberate stable promotion.
freshen-channel needs no code change. It runs --release <channel>
inside a worktree at the latest stable tag, where the build-lib.sh
at that tag is still the old version producing old-format labels;
the first stable cut after this commit will propagate the new format
to subsequent freshens (per the existing "build pipeline at the tag"
reproducibility policy).
AGENTS.md and CLAUDE.md updated.
339 lines
22 KiB
Markdown
339 lines
22 KiB
Markdown
# AGENTS.md — ZDDC
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Build all tools (writes to dist/ only)
|
|
sh build.sh
|
|
|
|
# Build single tool
|
|
sh tool/build.sh # archive | transmittal | classifier | mdedit | landing
|
|
|
|
# Cut a stable release (auto-increments patch version, tags, writes to website/releases/)
|
|
sh tool/build.sh --release
|
|
sh tool/build.sh --release 1.2.0 # explicit version
|
|
|
|
# Cut an alpha/beta channel build (mutable, no git tag)
|
|
sh tool/build.sh --release alpha
|
|
sh tool/build.sh --release beta
|
|
|
|
# Release all tools at once
|
|
sh build.sh --release [version|alpha|beta]
|
|
|
|
# Test all tools
|
|
npm test
|
|
|
|
# Test single tool
|
|
npx playwright test tool # archive | transmittal | classifier | mdedit
|
|
|
|
# Dev server (cache-busting HTTP, on port 8000)
|
|
./dev-server start
|
|
./dev-server stop
|
|
```
|
|
|
|
No lint, typecheck, or format commands exist — the project is plain sh + vanilla JS.
|
|
|
|
## Architecture
|
|
|
|
Five independent single-file HTML tools (`archive`, `transmittal`, `classifier`, `mdedit`, `landing`). Each compiles to one self-contained `.html` in `dist/` with all CSS and JS inlined — the first four name their output `dist/tool.html`; `landing` writes `dist/index.html` (it's served at `/` by `zddc-server`). Tools share a small set of canonical helpers in `shared/` (filename parsing, ZDDC filter UI, theme, help) — see "Shared modules" below.
|
|
|
|
```
|
|
tool/
|
|
css/ source stylesheets (concatenated in order)
|
|
js/ vanilla JS IIFEs (concatenated in order)
|
|
template.html placeholder markers: {{CSS_PLACEHOLDER}}, {{JS_PLACEHOLDER}}, {{BUILD_LABEL}}
|
|
build.sh assembles dist/tool.html
|
|
dist/tool.html generated output — committed with `git add -f`
|
|
|
|
shared/
|
|
base.css CSS tokens and primitives included first by every tool's CSS build
|
|
zddc.js canonical filename/folder/revision parsers, formatters, status validation
|
|
zddc-filter.js shared ZDDC project/status filter UI module
|
|
theme.js light/dark theme switcher
|
|
help.js shared help dialog module
|
|
build-lib.sh POSIX sh helpers (ensure_exists, concat_files, build_timestamp)
|
|
sourced by every tool's build.sh via: . "$root_dir/../shared/build-lib.sh"
|
|
|
|
website/
|
|
index.html current stable landing (root URL)
|
|
releases/
|
|
<tool>_v<X>.<Y>.<Z>.html immutable stable release archives
|
|
<tool>_stable.html -> ... symlink to current stable (highest semver)
|
|
<tool>_alpha.html mutable; overwritten by --release alpha
|
|
<tool>_beta.html mutable; overwritten by --release beta
|
|
bootstrap/
|
|
level1/<tool>.html same-origin level-1 stubs (4 tools, no landing)
|
|
track-stable/<tool>.html level-2 stubs that track the current-stable channel
|
|
track-alpha/<tool>.html level-2 stubs that track the alpha channel
|
|
track-beta/<tool>.html level-2 stubs that track the beta channel
|
|
|
|
bootstrap/
|
|
level1.html.tmpl per-project bootstrap template (relative ../<tool>.html)
|
|
level2.html.tmpl level-2 channel-tracking bootstrap template
|
|
README.md install / channel / pin docs
|
|
```
|
|
|
|
**Critical:** `dist/` files are gitignored but force-committed (`git add -f`). Never edit them directly.
|
|
|
|
## Shared CSS (`shared/base.css`)
|
|
|
|
Included as the **first** positional arg to every tool's `concat_files` CSS call. Provides:
|
|
- `:root` CSS custom properties — `--primary`, `--bg`, `--text`, `--border`, `--font`, etc.
|
|
- Brand color: `--primary: #2a5a8a` (matches zddc.varasys.io)
|
|
- Button primitive: `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-sm`, `.btn-lg`, `.btn-link`
|
|
- `.app-header` + `.app-header__title` chrome rules
|
|
- `.build-timestamp`, `.hidden`, `.truncate`, webkit scrollbars
|
|
|
|
**Do not** define these in any tool's own CSS — they come from shared.
|
|
|
|
**Toast CSS** lives in `classifier/css/base.css` only (classifier is the only tool that uses toasts).
|
|
|
|
## Transmittal CSS quirks
|
|
|
|
- `transmittal/css/base.css` overrides `html { font-size: 16px }` inside `@media screen` — this must stay. `shared/base.css` sets `14px`; transmittal's floating labels are rem-based and were designed for 16px.
|
|
- The floating label position is defined in `transmittal/css/forms.css`, not Tailwind classes. If adding new Tailwind classes to `template.html`, add them to `transmittal/css/utilities.css` too — there is no Tailwind build step.
|
|
|
|
## Build system rules
|
|
|
|
- Every `build.sh` sources `shared/build-lib.sh` first (provides `ensure_exists`, `concat_files`, `build_timestamp`). Set `root_dir` before sourcing.
|
|
- Build scripts use **POSIX sh** (`#!/bin/sh` with `set -eu`), not bash.
|
|
- `concat_files` accepts **positional args only** (not array names).
|
|
- `awk` processes `template.html`, replacing `{{PLACEHOLDER}}` markers and stripping CDN `<script>`/`<link>` tags (pattern: `https?://`)
|
|
- `{{BUILD_LABEL}}` is substituted in all five tools via `gsub` in awk (use `gsub`, not `print` — the placeholder is inline in an HTML line). Value is `Built: <timestamp> BETA` for dev builds, `v<version>` for stable releases, and `<channel> · <date> · <sha>` for alpha/beta channel builds; computed before the awk step. The shared `is_red` flag controls whether the label is wrapped in a red+bold `<span>` (true for dev/alpha/beta, false for stable).
|
|
- Cleans up temp files via `trap cleanup EXIT`
|
|
|
|
**`</` escaping is mandatory.** Any JS containing `</tag>` inside string or template literals will break inline `<script>` embedding. Run:
|
|
```bash
|
|
sed 's#</#<\\/#g' "$input_js" > "$safe_js"
|
|
```
|
|
Required for any new tool with vendor JS or JS containing HTML template literals.
|
|
|
|
## JS module pattern
|
|
|
|
All JS is vanilla, no bundlers. Files are IIFEs, registered on `window.app.modules`. Load order = declaration order in `build.sh`. `window.app` is the only global.
|
|
|
|
```javascript
|
|
(function() {
|
|
window.app.modules.mymodule = { ... };
|
|
})();
|
|
```
|
|
|
|
**Exception:** archive uses plain globals (`APP_STATE`, top-level functions) — not the IIFE/modules pattern.
|
|
|
|
## ZDDC filename parsers
|
|
|
|
All parsing/formatting goes through `shared/zddc.js`, exposed as `window.zddc`. Tools call it directly — no per-tool wrappers.
|
|
|
|
`window.zddc` exports:
|
|
- `parseFilename(name)` → `{ trackingNumber, revision, status, title, extension, valid } | null` (extension WITHOUT leading dot)
|
|
- `parseFolder(name)` → `{ date, trackingNumber, status, title, valid } | null`
|
|
- `parseRevision(rev)` → `{ base, modifier, modifierType, modifierNumber, isDraft, modifierIsDraft, full }`
|
|
- `compareRevisions(a, b)` → number (canonical sort order)
|
|
- `formatFilename(parts)` / `formatFolder(parts)` — round-trips parsed output
|
|
- `isValidStatus(code)` — accepts known status codes plus `---`
|
|
|
|
All file objects across tools use `file.trackingNumber` (string) and `file.extension` (string, **no leading dot**, e.g. `'pdf'` not `'.pdf'`). When concatenating into a filename, write `name + '.' + ext`.
|
|
|
|
Coverage lives in `tests/zddc.spec.js` (47 cases). Add new edge cases there, not in tool tests.
|
|
|
|
## Testing quirks
|
|
|
|
- Playwright + Chromium only (File System Access API requirement)
|
|
- Tests open `dist/tool.html` via `file://` protocol — **always build before testing**
|
|
- File System Access API is mocked via `page.addInitScript()` using `tests/fixtures/mock-fs-api.js`
|
|
- Use `waitUntil: 'load'` or `'domcontentloaded'` not `'networkidle'` — bundled scripts keep the network "active"
|
|
- Archive's `#noDirectoryMessage` empty-state overlay is `position: absolute; top: 50px` — it must clear the header or it will block button clicks in tests
|
|
|
|
## ZDDC filename convention
|
|
|
|
Format: `trackingNumber_revision (status) - title.extension`
|
|
|
|
- `trackingNumber`: no spaces or underscores (e.g. `123456-EL-SPC-2623`)
|
|
- `revision`: `A`, `B`, `0`; draft prefix `~`; modifiers `+C1`, `+B1`, `+N1`, `+Q1`
|
|
- `status`: `IFA IFB IFC IFD IFI IFP IFR IFU REC RSA RSB RSC RSD RSI` or `---`
|
|
- Folder names prefix with date: `2025-10-31_trackingNumber (status) - title`
|
|
|
|
## Git workflow
|
|
|
|
- Feature-branch workflow; squash-merge feature branches to `main`
|
|
- Conventional commits: `feat(archive): ...`, `fix(transmittal): ...`
|
|
- Release tags: `archive-v1.0.0` (per-tool semver)
|
|
- Commit dist files: `git add -f tool/dist/tool.html`
|
|
|
|
### Releasing — channels and layout
|
|
|
|
Three channels. Versioning is **pre-release semver**: stable owns clean `vX.Y.Z`; alpha and beta carry `vX.Y.Z-alpha` / `vX.Y.Z-beta` suffixes (with a `.N` counter on the zddc-server image only — see "zddc-server release flow" below). The next-stable target X.Y.Z is patch-bumped from the latest clean `<tool>-vX.Y.Z` tag, so the on-page label always communicates which stable the alpha/beta is working toward.
|
|
|
|
- **Stable**: versioned, immutable. `sh tool/build.sh --release [version]` writes `website/releases/<tool>_v<version>.html`, refreshes the `<tool>_stable.html` symlink, and tags `<tool>-v<version>`. Skips automatically if source has not changed since the latest tag. Pass an explicit version to override auto-increment. Label: `vX.Y.Z` (black).
|
|
- **Beta**: mutable. `sh tool/build.sh --release beta` overwrites `website/releases/<tool>_beta.html` in place. No tag — alpha and beta artifacts are mutable files, not immutable per-build snapshots. Label: `vX.Y.Z-beta · <date> · <sha>` (red), where `X.Y.Z` is the next-stable target.
|
|
- **Alpha**: mutable, analogous. `sh tool/build.sh --release alpha`. **Also**: every plain (non-release) `tool/build.sh` invocation copies the just-built dist file into `website/releases/<tool>_alpha.html`, so the alpha hyperlinks on the website always serve the latest dev build. Plain copy (not symlink) so the file is reachable regardless of how the web server mounts the working tree (the canonical Caddy setup at `/etc/containers/systemd/caddy.container` mounts only `website/` read-only and cannot follow `../<tool>/dist/` symlinks). Side-effect: every dev build dirties the corresponding `_alpha.html` in `website/releases/`; commit those alongside the source change or `git checkout` them before pushing. Plain-build alpha pages carry the dev label `vX.Y.Z-alpha · <ts> · <sha>[-dirty]` (red, with the full timestamp + dirty marker so iterative dev builds are distinguishable from formal `--release alpha` cuts, which use `<date>` only and never carry `-dirty`).
|
|
|
|
Stable releases do **not** automatically clobber `<tool>_alpha.html` / `<tool>_beta.html` — those keep whatever was last built into them. Use `./freshen-channel <tool> <channel>` (see "Freshen helper" below) to drag a channel forward to current stable; never `git checkout` the main worktree by hand for this.
|
|
|
|
After cutting a stable release, run `git push --tags` to publish the tag.
|
|
|
|
The "skip if no source change since last tag" guard for stable releases compares **HEAD** to the latest tag — uncommitted working-tree changes are invisible. If you edit a tool and want a stable release to actually fire, commit the change first; otherwise the build prints `no source changes since <tool>-vX.Y.Z — skipping` and exits 0. Alpha and beta channel builds always rebuild (no skip check).
|
|
|
|
Agents must **never** write to `website/releases/<tool>_v*.html`, `website/releases/<tool>_stable.html`, `website/releases/<tool>_beta.html`, or `website/index.html` directly — always go through `--release` or `./freshen-channel`. (The `<tool>_alpha.html` files are an exception: every plain build rewrites them with a copy of the current dist, as described above.)
|
|
|
|
`landing/build.sh --release <version>` additionally writes `website/index.html` (the root URL of zddc.varasys.io).
|
|
|
|
### Channel discipline (MUST rules)
|
|
|
|
The build system does not enforce these. Treating channels carelessly defeats the point of having three. Be disciplined.
|
|
|
|
1. **Stable doesn't regress.** No known-broken features that worked in the previous stable. If you ship `v0.0.5` with a bug, the path forward is `v0.0.6` with a fix — never edit `v0.0.5` in place. Stable files are immutable.
|
|
2. **No backports.** Don't try to patch an older stable version. Always cut a new stable at a higher version. Users pinned to the old version stay pinned by their own choice; they can move forward when they want.
|
|
3. **Alpha and beta are mutable.** Document this anywhere you invite users to test them. Pinning `?v=alpha` (or `_alpha.html`) in a production deployment is a mistake; it gets rebuilt without notice.
|
|
4. **Stale-channel rule.** Users tracking alpha (or beta) MUST never see a build older than current stable. After every stable release, run `./freshen-channel <tool> alpha` and `./freshen-channel <tool> beta` so each channel is at-least-current. This is not optional.
|
|
5. **Hotfix path.** For critical bugs: fix on `main`, cut a new stable (no beta soak required), then freshen alpha + beta. Tag the commit message `fix:` or include "hotfix" so the intent is visible in `git log`.
|
|
6. **Beta soak before promoting (recommended).** Give a beta a few days of exposure before cutting the same code as stable. Not enforced; use judgment for trivial changes.
|
|
|
|
### Freshen helper
|
|
|
|
`./freshen-channel <tool> <channel>` rebuilds the alpha or beta channel of a tool from its current stable tag. Use it after every stable release (rule 4 above) and any other time alpha/beta has fallen behind stable.
|
|
|
|
```sh
|
|
./freshen-channel archive alpha
|
|
./freshen-channel transmittal beta
|
|
```
|
|
|
|
What it does:
|
|
|
|
1. Finds the latest `<tool>-v*` tag.
|
|
2. Creates a temporary git worktree at that tag — does **not** touch the main worktree's HEAD or working tree.
|
|
3. Runs `<tool>/build.sh --release <channel>` inside the worktree.
|
|
4. Copies the resulting `<tool>_<channel>.html` into the main repo's `website/releases/`.
|
|
5. Removes the worktree.
|
|
|
|
The on-page label of the freshened build is `v<next-stable>-<channel> · <today> · <stable-tag-sha>` — the version reflects the next-stable target (patch+1 from current stable, since the freshen establishes the baseline for the upcoming pre-release window) and the SHA pins which stable was used as the source, recoverable via `git checkout`.
|
|
|
|
Note: the build pipeline used is the one **at the tag**, not on `main`. That is intentional (pure reproducibility). If you have made build-system improvements since stable was cut and want the freshen to use them, cut a new stable first.
|
|
|
|
### Bootstrap stubs
|
|
|
|
`build.sh` regenerates `website/bootstrap/` on every invocation:
|
|
|
|
- `bootstrap/level1/<tool>.html` — 4 same-origin level-1 stubs (archive, transmittal, classifier, mdedit; landing has no level-1 stub since it only lives at deployment root).
|
|
- `bootstrap/track-{alpha,beta,stable}/<tool>.html` — 5 level-2 stubs per channel, hardcoded to fetch the named channel from `zddc.varasys.io/releases/`.
|
|
|
|
End users install via copy-paste shell snippets on the home page's "Install on your server" section — each snippet `curl`s the relevant stubs (or stable HTMLs, for the self-contained option) into the operator's deployment directory.
|
|
|
|
See `bootstrap/README.md` for the install / pin / audit story.
|
|
|
|
### Worktrees
|
|
|
|
Use `git worktree` to run multiple agents on separate branches simultaneously without filesystem collisions.
|
|
|
|
- Worktrees live at `~/src/zddc-<branch-name>` (sibling of the main clone)
|
|
- Before starting work on a feature branch, check `git worktree list`; if no worktree exists, create one: `git worktree add ~/src/zddc-<branch-name> -b <branch-name>`
|
|
- All edits, builds (`sh build.sh`), and tests (`npm test`) run from within the worktree directory — build scripts use relative paths so this works correctly
|
|
- The `dist/` force-commit rule (`git add -f`) applies per-worktree
|
|
- After the branch is merged, clean up: `git worktree remove ~/src/zddc-<branch-name>` then delete the branch
|
|
- Never run `git checkout` or `git switch` inside a worktree that another agent may be using
|
|
|
|
## Transmittal-specific
|
|
|
|
- Two-phase hydration: `populateStatic()` before publish, `hydrate()` on load of published file
|
|
- Reactive state via Proxy — `app.state.mode = 'view'` auto-notifies subscribers
|
|
- Runtime CDN loads (jszip, docx-preview, xlsx) are allowed only for the optional DOCX/XLSX preview; core features work offline
|
|
- Published payload stored in `<script id="transmittal-data" type="application/json">`
|
|
|
|
## mdedit-specific
|
|
|
|
- `css/tailwind-utils.css` is a pre-generated static subset (~80 classes). Add new Tailwind classes here; do not re-run Tailwind.
|
|
- Toast UI Editor v3.2.2 is bundled in `vendor/`; `template.html` loads it from CDN for dev convenience
|
|
- `</` escaping is essential: `sed 's#</#<\\/#g'` runs on both app JS and vendor JS at build time
|
|
|
|
## zddc-server
|
|
|
|
Go HTTP server sub-project living at `zddc/`. Replaces `caddy file-server --browse` for ZDDC archives.
|
|
|
|
### Build
|
|
|
|
```sh
|
|
# Build the container image (from the zddc/ directory)
|
|
podman build -t zddc-server zddc/
|
|
|
|
# Or inside the zddc/ directory:
|
|
podman build -t zddc-server .
|
|
```
|
|
|
|
### Run (development)
|
|
|
|
```sh
|
|
ZDDC_DATA_DIR=/path/to/your/archive podman-compose -f zddc/podman-compose.yaml up --build
|
|
```
|
|
|
|
### Key environment variables
|
|
|
|
| Variable | Default | Purpose |
|
|
|---|---|---|
|
|
| `ZDDC_ROOT` | *(required)* | Path to served file tree |
|
|
| `ZDDC_ADDR` | `:8443` | Bind address |
|
|
| `ZDDC_EMAIL_HEADER` | `X-Auth-Request-Email` | Header set by upstream proxy with user email (oauth2-proxy / nginx auth-request convention) |
|
|
| `ZDDC_INDEX_PATH` | `.archive` | Virtual archive index URL segment |
|
|
| `ZDDC_LOG_LEVEL` | `info` | Logging verbosity |
|
|
| `ZDDC_CORS_ORIGIN` | `https://zddc.varasys.io` | Comma-separated CORS allowlist; empty value disables CORS. Default lets tools served from zddc.varasys.io call back into a customer-deployed server. |
|
|
|
|
### Release tagging
|
|
|
|
`release-image.sh` auto-derives the version (from the latest clean
|
|
`zddc-server-vX.Y.Z` tag) and creates the git tag itself, so the
|
|
operator no longer cuts a tag manually before running the script:
|
|
|
|
```sh
|
|
sh release-image.sh # alpha cut, version auto-derived
|
|
sh release-image.sh alpha # same
|
|
sh release-image.sh beta # beta cut
|
|
sh release-image.sh stable # stable cut, patch++ from latest stable
|
|
sh release-image.sh stable 0.1.0 # stable cut, explicit version
|
|
```
|
|
|
|
**Default channel is `alpha`** so `:stable` never advances by accident
|
|
during active development. Pass `beta` to soak; pass `stable` only when
|
|
deliberately promoting. The script tags the commit but does NOT push
|
|
git history — finish with `git push origin <branch>` and `git push --tags` (or push the specific tag).
|
|
|
|
**Versioning** — pre-release semver. Stable cuts get clean `vX.Y.Z`
|
|
tags and `:vX.Y.Z` image tags. Alpha and beta cuts get
|
|
`vX.Y.Z-alpha.N` / `vX.Y.Z-beta.N` where `X.Y.Z` is the next patch
|
|
of the latest clean stable, and `N` is a per-channel counter that
|
|
resets when stable advances. Example sequence (current stable v0.0.7):
|
|
|
|
```
|
|
alpha → v0.0.8-alpha.1
|
|
alpha → v0.0.8-alpha.2
|
|
beta → v0.0.8-beta.1
|
|
alpha → v0.0.8-alpha.3 (alpha and beta count separately)
|
|
stable → v0.0.8 (counter resets at next-patch advance)
|
|
alpha → v0.0.9-alpha.1
|
|
```
|
|
|
|
Cascades (unchanged):
|
|
|
|
- `alpha` → `:<version> :alpha`
|
|
- `beta` → `:<version> :beta :alpha`
|
|
- `stable` → `:<version> :stable :beta :alpha`
|
|
|
|
Pre-release semver ordering (`0.0.8-alpha.1 < 0.0.8-alpha.2 <
|
|
0.0.8-beta.1 < 0.0.8`) is honored by all standard tooling — registry
|
|
tag sorting, `git tag --sort=-v:refname`, `sort -V`, npm, cargo —
|
|
so consumers can pin or compare versions without surprises.
|
|
|
|
Prerequisite: `podman login codeberg.org` (one-time, with a Codeberg
|
|
personal token scoped `package:write`).
|
|
|
|
There is no CI for this — `.woodpecker.yml` was removed in favor of
|
|
local-build-and-push because solo workflow benefits from one canonical
|
|
path that fails loudly and visibly on the developer's terminal.
|
|
|
|
### Notes
|
|
|
|
- No external test framework yet — Go unit tests run with `go test ./...` inside `zddc/` (requires Go 1.24+)
|
|
- The container image does NOT require Go on the host — the Containerfile uses a multi-stage build
|
|
- Portfolio files (`*.portfolio`) in the served tree appear as virtual group directories
|
|
- Every folder exposes a `.archive` virtual directory backed by the same global index — the depth in the URL only matters so HTML produced for offline use can reach `.archive/` via `../.archive/` relative links and have the browser resolve them before the request hits the server. The flat listing emits two redirect entries per tracking number: `<tracking>.html` (highest base rev) and `<tracking>_<rev>.html` (each specific base rev). Both redirect to the first chronologically received copy of the named revision. Modifier files (`<tracking>_<rev>+C1.html` etc.) remain reachable via the resolver but are not surfaced in the listing — they're return traffic, not primary documents. ACL is the only filter: the listing endpoint is gated by the contextPath's `.zddc` chain, and each entry is then filtered against the ACL of its resolved file's directory; per-target denials return 404 rather than 403 to avoid leaking that the tracking number exists in another subtree
|
|
- ACL is enforced via cascading `.zddc` YAML files; authentication is delegated to the upstream proxy via the `X-Auth-Request-Email` header (configurable with `ZDDC_EMAIL_HEADER`)
|
|
- `.zddc` schema also supports a top-level `admins:` glob list, peer to `acl.allow`/`acl.deny`. Honored **only** at the root `.zddc` (subdir `admins` entries are ignored to prevent privilege escalation via subtree write access). Drives the built-in debug dashboard at `/.admin/` (sub-routes: `/whoami`, `/config`, `/logs`); non-admin requests get 404 so the page is invisible. See `zddc/README.md` § "Admin Debug Page".
|
|
- **Reserved entry prefixes** under `ZDDC_ROOT`: `.`-prefixed entries are excluded from listings AND 404 on direct fetch (only `.archive` and `.admin` are exempt) — for invisible side-state like dev-shell home dirs. `_`-prefixed entries are excluded from listings only — for operator scaffolding like the `_template/` directory created by the self-contained install snippet, still reachable by direct URL. Drop side-state under `_` if it should be linkable; under `.` if it should be unreachable.
|