Renames build.sh → build and replaces the --release flag form with
subcommands:
./build cut alpha (default; active dev iteration)
./build beta cut beta (cascades alpha → beta)
./build release cut stable (coordinated next version)
./build release X.Y.Z cut stable at explicit version
./build help
The contract shift: there's no longer a "plain dev build that doesn't
touch channels" at the top level. Every full-stack build is a publish
action — running ./build IS active dev iteration, which is what alpha
already meant. To iterate on one tool without writing to the website
worktree, use the per-tool sh tool/build.sh (unchanged).
Output continues to land in ${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}
and nothing is pushed automatically. Commit + push the website branch
yourself when you want to publish. Stable cuts still tag locally on
main; tags push separately too.
Behind the scenes: the export of ZDDC_DEPLOY_RELEASES_DIR is moved
above the per-tool build.sh invocations so children inherit it. The
prior "if RELEASE_CHANNEL else write_zddc_server_stubs_all" branch is
collapsed since RELEASE_CHANNEL is always set under the new CLI.
Docs (CLAUDE.md, AGENTS.md, ARCHITECTURE.md, zddc/README.md) updated
to reference ./build everywhere; the per-tool sh tool/build.sh refs
stay (they're a separate, narrower entry point).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
363 lines
28 KiB
Markdown
363 lines
28 KiB
Markdown
# AGENTS.md — ZDDC
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# ── Lockstep release driver: every invocation is a publish action. ────────
|
|
# Alpha is the default since "alpha = active dev iteration" and that's
|
|
# what running the build IS. Coordinated version (for release): max
|
|
# across all six tools' latest tags + 1. Workflow:
|
|
# alpha = active dev → beta = ready for general testing → stable = ship
|
|
|
|
./build # cut alpha (default) — cascades nothing
|
|
./build beta # cut beta — cascades alpha → beta
|
|
./build release # cut stable — coordinated next version,
|
|
# cascades all to the new version
|
|
./build release 1.2.0 # cut stable at explicit version
|
|
./build help # usage
|
|
|
|
# Build a single HTML tool's dist/ for testing (does NOT touch the
|
|
# website worktree's release artifacts):
|
|
sh tool/build.sh # archive|transmittal|classifier|mdedit|landing
|
|
|
|
# Single-tool release (rare; prefer the lockstep ./build above so
|
|
# versions don't drift between tools). Same flag form as before.
|
|
sh tool/build.sh --release [<version>|alpha|beta]
|
|
./freshen-channel <tool> <channel> # rebuild one tool's alpha/beta from its current stable tag
|
|
|
|
# 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.
|
|
|
|
The build ends with a **channel-link verifier** that asserts every
|
|
`<tool>_{stable,beta,alpha}.html` (and zddc-server's per-platform binary
|
|
mirrors + stub pages) resolves. Build fails if any link is dangling.
|
|
Output goes to `${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}`
|
|
— the website branch's worktree, what Caddy serves. **Nothing is
|
|
pushed automatically;** review with `git -C ~/src/zddc-website status`,
|
|
commit + push the website branch yourself when ready.
|
|
|
|
## 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 lives on the `website` orphan branch of this same repo
|
|
# (NOT in main's tree). Worktree typically at ~/src/zddc-website/:
|
|
# index.html, reference.html, css/, js/, img/ hand-edited content
|
|
# releases/
|
|
# index.html regenerated by `./build`
|
|
# <tool>_v<X.Y.Z>.html per-version (committed, immutable)
|
|
# <tool>_v<X.Y>.html -> ... symlink chain (regular git symlinks)
|
|
# <tool>_stable.html -> ... channel mirror, follows latest stable
|
|
# <tool>_{beta,alpha}.html -> ... channels (cascade to stable when idle)
|
|
# zddc-server_v<X.Y.Z>_<platform> per-platform binary (LFS-tracked)
|
|
# zddc-server_<channel>_<platform> channel binary mirror (symlink)
|
|
# zddc-server_<X>.html stub page surfacing 4 platform DLs
|
|
# .gitattributes LFS rules for binaries
|
|
|
|
helm/
|
|
zddc-server-prod/ production-shaped Helm chart (compiles from source via init container)
|
|
zddc-server-dev/ dev-shaped variant (tracks main HEAD; debug-level logging; faster probes)
|
|
README.md chart design rationale + quick-start
|
|
```
|
|
|
|
**Critical:** `dist/` files are gitignored. They're the canonical built artifact for testing and the source for `--release` writes into `~/src/zddc-website/releases/`, but they aren't checked in. Never edit them directly.
|
|
|
|
**Release artifacts live on the `website` orphan branch** of this same Codeberg repo, not on `main`. A `git worktree` of that branch — typically at `~/src/zddc-website/` — is what the system Caddy bind-mounts and serves at `zddc.varasys.io/`. The build pipeline writes its release outputs to `${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}` directly, then the operator commits + pushes that worktree separately from `main`. Per-version HTML and per-version zddc-server binaries are real bytes (binaries are LFS-tracked; HTML stays regular git); partial-version pins (`_v<X.Y>`, `_v<X>`) and channel mirrors (`_stable`, `_beta`, `_alpha`) are symlinks. `shared/build-lib.sh` provides `promote_release` (HTML tools) and `promote_zddc_server` (binaries + matching stub pages); the top-level `./build` calls them in lockstep. No Codeberg release-asset publication anymore; everything serves from `zddc.varasys.io/releases/`.
|
|
|
|
## 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: `<tool>-v<X.Y.Z>` per tool, all six sharing the same X.Y.Z on a coordinated cut (e.g. `archive-v0.0.8`, `transmittal-v0.0.8`, `classifier-v0.0.8`, `mdedit-v0.0.8`, `landing-v0.0.8`, `zddc-server-v0.0.8`)
|
|
- Commit dist files: `git add -f tool/dist/tool.html`
|
|
- Commit zddc-server binaries (per-version + symlinks) on the `website` branch's worktree (`~/src/zddc-website/releases/`) — they're LFS-tracked there alongside the HTML tool releases. Push that branch separately from `main`
|
|
|
|
### Releasing — lockstep, channels, layout
|
|
|
|
**Lockstep convention.** Every release cut bumps all six artifacts (5 HTML tools + zddc-server) to the same version, even if a tool didn't change. Per-tool independent versions are gone. The coordinated next-stable target is `max(latest tag across all six tools) + 1` — `_coordinated_next_stable` in `shared/build-lib.sh`. Channel cuts (alpha/beta) follow the same lockstep — every tool's channel mirror is overwritten in step. Three channels, ordered: **alpha** (dev iteration) → **beta** (general testing) → **stable** (ship).
|
|
|
|
**Storage model.** All release artifacts live under the `website` orphan branch of this repo (worktree typically at `~/src/zddc-website/releases/`) and are served from `zddc.varasys.io/releases/`. The `website` branch's history is independent of `main`. No Codeberg release assets, no third-party mirrors.
|
|
|
|
| Artifact | Type | Layout |
|
|
|---|---|---|
|
|
| `<tool>_v<X.Y.Z>.html` | real, immutable | per-version HTML for each of archive, transmittal, classifier, mdedit, landing |
|
|
| `<tool>_v<X.Y>.html`, `<tool>_v<X>.html` | symlinks | partial-version pins |
|
|
| `<tool>_<channel>.html` | symlink (or real bytes during active channel dev) | mutable channel mirror per tool, channel ∈ {stable, beta, alpha} |
|
|
| `zddc-server_v<X.Y.Z>_<platform>` | real binary | per-version cross-compiled binary, platform ∈ {linux-amd64, darwin-amd64, darwin-arm64, windows-amd64.exe} |
|
|
| `zddc-server_v<X.Y>_<platform>`, `zddc-server_v<X>_<platform>`, `zddc-server_<channel>_<platform>` | symlinks (or real bytes during active channel dev) | partial-pin and channel mirrors per platform — same cascade as the HTML tools |
|
|
| `zddc-server_<X>.html` | generated stub page | per-version / per-channel; lists the four platform downloads. This is what the matrix-cell link points at — one stub fans out to four binaries |
|
|
| `index.html` | regenerated by `build.sh` | matrix table, one column per tool, one row per release |
|
|
|
|
**Single point of truth.** `./build release` is the canonical lockstep cut. It forwards each HTML tool's build with the agreed version, then `promote_zddc_server` (in `shared/build-lib.sh`) copies the freshly cross-compiled binaries into `~/src/zddc-website/releases/` with the matching symlink chain, then `write_zddc_server_stubs_all` regenerates every stub page, then `build_releases_index` rewrites the matrix, then `verify_channel_links` asserts nothing dangles.
|
|
|
|
- **Stable** (`./build release` or `--release X.Y.Z`): Writes per-version HTML for the five HTML tools + per-version binaries for zddc-server (real bytes, immutable). Refreshes 5 symlinks per HTML tool + 5 symlinks per zddc-server platform → the new version. Tags all six: `<tool>-v<X.Y.Z>`. Cascade: stable cut means beta and alpha both reset to stable for every tool. Skips silently if source for an HTML tool hasn't changed since the latest stable tag (the binary always builds).
|
|
- **Beta** (`./build beta`): Overwrites `<tool>_beta.html` with dist bytes for each HTML tool, and `zddc-server_beta_<platform>` with each platform's binary. Cascade: `<tool>_alpha.html` → `<tool>_beta.html` and `zddc-server_alpha_<platform>` → `zddc-server_beta_<platform>` (symlinks). No tag.
|
|
- **Alpha** (`./build`): Overwrites only the alpha mirrors, all six tools. No tag, no other side-effects.
|
|
- **Plain dev builds** (no `--release`): produce `tool/dist/<tool>.html` for HTML tools and `zddc/dist/zddc-server-<platform>` binaries; do NOT touch `~/src/zddc-website/releases/`. The matrix index and stub pages still get regenerated from whatever `~/src/zddc-website/releases/` contains, so the build is idempotent for repeated dev runs.
|
|
|
|
On-page `{{BUILD_LABEL}}` format (HTML tools only — zddc-server's version comes from the binary itself):
|
|
|
|
- Plain dev: `vX.Y.Z-alpha · <full-ts> · <sha>[-dirty]` (red), where X.Y.Z is the per-tool next-stable target.
|
|
- `--release alpha`: `vX.Y.Z-alpha · <date> · <sha>` (red).
|
|
- `--release beta`: `vX.Y.Z-beta · <date> · <sha>` (red).
|
|
- `--release [version]`: `v<X.Y.Z>` (black).
|
|
|
|
After cutting a stable release, `git push origin main && git push origin --tags` to publish the new version files + symlinks + every per-tool tag in lockstep.
|
|
|
|
### Channel discipline (MUST rules)
|
|
|
|
The build enforces lockstep mechanically (one command bumps all six). The rules below are still on you.
|
|
|
|
1. **Stable doesn't regress.** No known-broken features that worked in the previous stable. If `v0.0.5` ships with a bug, the path forward is `v0.0.6` with a fix — never edit a previously-published per-version file in place. Stable per-version files are immutable.
|
|
2. **Lockstep is the contract.** Don't cut a single tool's release without bumping the rest. The HTML tool's standalone `--release` flag still exists as an escape hatch but emits a tag that immediately drifts out of sync with the others.
|
|
3. **No backports.** Always cut a new stable at a higher version. Users pinned to an old version stay pinned by choice.
|
|
4. **Alpha and beta are mutable.** Document this anywhere you invite users to test them. Pinning a deployment to a channel mirror means it gets rebuilt without notice. For reproducibility, pin to a per-version URL — `<tool>_v0.0.5.html` or `zddc-server_v0.0.5.html`.
|
|
5. **Cascade is automatic.** Stable cut → beta + alpha mirrors reset to stable (per-tool HTML AND per-platform zddc-server). Beta cut → alpha → beta. "No active beta" silently shows current stable. No freshen step required after a stable release.
|
|
6. **Hotfix path.** For critical bugs: fix on `main`, cut a new stable. Tag the commit message `fix:` or include "hotfix" so intent is visible in `git log`.
|
|
7. **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 — useful when you want a channel to advance to current stable code without doing active dev on it (e.g. after upstream dependency changes). Most of the time you don't need it: the cascade rule (rule 5 above) means a stable cut already resets the downstream channel symlinks. Use this when you specifically want a fresh build with a new on-page label timestamp instead of a symlink.
|
|
|
|
```sh
|
|
./freshen-channel archive alpha
|
|
./freshen-channel transmittal beta
|
|
```
|
|
|
|
What it does:
|
|
|
|
1. Finds the latest `<tool>-v*` clean stable 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, which overwrites `<tool>_<channel>.html` with the freshly-built bytes. (Note: this is in the worktree, not on main — you'll need to commit the resulting changes back to main afterward.)
|
|
4. Removes the worktree.
|
|
|
|
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.
|
|
|
|
### Install model
|
|
|
|
No install script. Two paths:
|
|
|
|
- **Local** — download a tool `.html` from `https://zddc.varasys.io/releases/` and open it. Done.
|
|
- **Server** (`zddc-server`) — every tool is `//go:embed`'d into the binary at compile time (the current-stable build). The server virtually serves them at folder-name-driven paths:
|
|
- `archive.html` at every directory (multi-project, project, archive, vendor levels)
|
|
- `classifier.html` in any `Incoming`/`Working`/`Staging` directory and its subtree
|
|
- `mdedit.html` in any `Working` directory and its subtree
|
|
- `transmittal.html` in any `Staging` directory and its subtree
|
|
- `index.html` (landing) only at the deployment root
|
|
|
|
See `internal/apps/availability.go`. Outside these locations, requesting `<app>.html` returns 404 (just like any other missing file).
|
|
|
|
To override at any level, either:
|
|
1. Drop a real `<app>.html` file at the path → static handler serves it (highest priority).
|
|
2. Write an `apps:` entry in any `.zddc` along the path. Spec is one of `stable`/`beta`/`alpha`/`v0.0.4`/`v0.0`/`v0`/full URL/local path. Closer-to-leaf entries win.
|
|
|
|
URL sources fetch once and cache forever in `<ZDDC_ROOT>/_app/<host>/<path>`. To force a re-fetch, delete the cache file. No background refresh, no SHA-256 verification, no admin UI. If a configured URL fetch fails, the server falls back to the embedded copy and emits a one-time WARN log.
|
|
|
|
Operators audit by reading the `X-ZDDC-Source` response header: `fetch:URL` / `cache:URL` / `path:/abs` / `embedded:<app>@<build>`. Direct URL access to `/_app/...` is blocked at the dispatch layer.
|
|
|
|
**Runtime mode detection** in archive is independent of install: it auto-detects multi-project / project-root / in-archive from `?projects=` plus folder shape. The other tools don't care where they live.
|
|
|
|
### 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 (`./build`), 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
|
|
|
|
zddc-server ships as a cross-compiled binary, not a container image. There's no Containerfile or compose file in this repo (the chart Dockerfiles compile from source at deploy time at the right tag).
|
|
|
|
```sh
|
|
# Compile a local binary for the host platform (requires Go 1.24+)
|
|
(cd zddc && go build -o zddc-server ./cmd/zddc-server)
|
|
|
|
# Or run directly without producing a binary
|
|
(cd zddc && go run ./cmd/zddc-server)
|
|
```
|
|
|
|
The repo's top-level `./build` cross-compiles the four release binaries (linux/amd64, darwin/amd64, darwin/arm64, windows/amd64) into `zddc/dist/` via a containerized Go toolchain (podman or docker). On `--release` it also promotes those binaries to `~/src/zddc-website/releases/` with the matching symlink chain and stub pages — same lockstep flow as the HTML tools.
|
|
|
|
### Run (development)
|
|
|
|
```sh
|
|
ZDDC_ROOT=/path/to/your/archive ZDDC_TLS_CERT=none ZDDC_ADDR=:8080 \
|
|
go run ./cmd/zddc-server
|
|
```
|
|
|
|
For a release binary downloaded from `zddc.varasys.io/releases/`:
|
|
|
|
```sh
|
|
curl -O https://zddc.varasys.io/releases/zddc-server_stable_linux-amd64
|
|
chmod +x zddc-server_stable_linux-amd64
|
|
ZDDC_ROOT=/path/to/your/archive ZDDC_TLS_CERT=none ZDDC_ADDR=:8080 \
|
|
./zddc-server_stable_linux-amd64
|
|
```
|
|
|
|
### 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
|
|
|
|
zddc-server has no separate release script anymore. The top-level `./build release [version|alpha|beta]` is the canonical path: it cross-compiles the binaries inside the containerized Go toolchain, copies them into `~/src/zddc-website/releases/` with the lockstep symlink chain (one set of symlinks per platform), regenerates the per-version + per-channel stub pages, refreshes the matrix, and tags `zddc-server-v<X.Y.Z>` alongside the five HTML-tool tags.
|
|
|
|
```sh
|
|
./build release # lockstep stable, coordinated next version
|
|
./build release 1.2.0 # lockstep stable, explicit version
|
|
./build # lockstep alpha cut for everything
|
|
./build beta # lockstep beta cut for everything
|
|
```
|
|
|
|
The script tags every tool but does NOT push — finish with `git push origin main && git push origin --tags`.
|
|
|
|
**Versioning** — clean semver. Stable cuts emit one `<tool>-vX.Y.Z` tag per tool, all six sharing the same X.Y.Z. No `-alpha.N` / `-beta.N` counter tags — channel URLs are stable URLs by design. Historical per-tool independent tags (`archive-v0.0.2`, `zddc-server-v0.0.7`, etc.) stay as artifacts; the next coordinated cut jumps every tool to the same number.
|
|
|
|
**Binary distribution** — `website/releases/zddc-server_<X>_<platform>` are real static files served from `zddc.varasys.io/releases/`. No Codeberg release assets, no `$CODEBERG_TOKEN`, no third-party mirror. The matrix-cell link points at `zddc-server_<X>.html`, a generated stub page that surfaces the four platform downloads in one click.
|
|
|
|
There is no CI for this — solo workflow benefits from one canonical
|
|
local 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+)
|
|
- Portfolio files (`*.portfolio`) in the served tree appear as virtual group directories
|
|
- Every folder under a project exposes a `.archive` virtual directory backed by that **project's** index bucket — the project is the first slash-separated segment of the contextPath. Depth within a project doesn't change scope: `/ProjectA/sub/sub/.archive/X.html` resolves the same as `/ProjectA/.archive/X.html`, just with a different URL prefix on the listing entries. 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 within that project. 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. `/.archive/` at the very root has no project segment and returns 404 — stable references must include the project directory. Within one project, two different files claiming the same `(tracking, rev)` are an authoring mistake; chronological winner still wins, but a `WARN` is emitted with both paths. ACL is enforced twice: 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".
|
|
- `GET /.auth/admin` is a **forward_auth target** for upstream proxies — returns 200 if the request's `X-Auth-Request-Email` is in the root `.zddc` `admins:` list, 403 otherwise. No body, no UI. Used by the dev-shell pod's Caddy to gate `/devshell/*` (code-server) on root-admin status without code-server learning about auth. zddc-server's own routes use the regular `.zddc` cascade ACL — they do NOT go through this endpoint.
|
|
- **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.
|