Add ./freshen-channel <tool> <channel> at the repo root for the
"drag alpha/beta forward to current stable" workflow. The script
uses a temporary git worktree at the latest <tool>-v* tag so the
main worktree's HEAD is never touched — no checkout, no stash, no
race against in-progress dev. Build runs inside the worktree, the
resulting <tool>_<channel>.html is copied back into the main
repo's website/releases/, worktree is removed.
The on-page label of a freshened build is `<channel> · <today> ·
<stable-tag-sha>` — the SHA pins which stable was the source, so
anyone debugging can `git checkout <sha>` to reproduce.
Smoke-tested:
./freshen-channel archive alpha → archive_alpha.html with
"alpha · 2026-04-27 · ea385b5"
./freshen-channel transmittal beta → transmittal_beta.html with
"beta · 2026-04-27 · ea385b5"
./freshen-channel foobar alpha → usage error
./freshen-channel archive stable → usage error
AGENTS.md gains a "Channel discipline (MUST rules)" subsection
codifying the protocol the build system can't enforce:
1. Stable doesn't regress — files are immutable; bump for fixes.
2. No backports — bump and let users update pins.
3. Alpha/beta are mutable — never pin in production.
4. Stale-channel rule — after every stable release, freshen alpha
and beta so neither is older than current stable. NOT optional.
5. Hotfix path — direct stable cut allowed, no beta soak required;
freshen alpha + beta after.
6. Beta soak (recommended) — a few days exposure before promoting.
Plus a "Freshen helper" subsection documenting the script.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
296 lines
16 KiB
Markdown
296 lines
16 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>_latest.html -> ... symlink to current stable (highest semver)
|
|
<tool>_alpha.html mutable; overwritten by --release alpha
|
|
<tool>_beta.html mutable; overwritten by --release beta
|
|
install.zip drop-in self-contained install (5 stable HTMLs + _template/ stubs)
|
|
track-latest.zip level-2 stubs that track the current-stable channel
|
|
track-alpha.zip level-2 stubs that track the alpha channel
|
|
track-beta.zip 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:
|
|
|
|
- **Stable**: versioned, immutable. `sh tool/build.sh --release [version]` writes `website/releases/<tool>_v<version>.html`, refreshes the `<tool>_latest.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.
|
|
- **Beta**: mutable. `sh tool/build.sh --release beta` overwrites `website/releases/<tool>_beta.html` in place. No tag. The on-page label is `beta · <date> · <sha>` so the source is recoverable from git via the SHA.
|
|
- **Alpha**: mutable, analogous. `sh tool/build.sh --release alpha`.
|
|
|
|
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/` or `website/index.html` directly — always go through `--release` or `./freshen-channel`.
|
|
|
|
`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 `<channel> · <today> · <stable-tag-sha>` — 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 zips
|
|
|
|
`build.sh` regenerates three downloadable zips into `website/` on every invocation:
|
|
|
|
- `install.zip` — 5 current-stable HTMLs at root + `_template/` directory containing 4 level-1 bootstrap stubs (per-project use). Skipped if any tool has no stable release yet.
|
|
- `track-{alpha,beta,latest}.zip` — 5 level-2 stubs each, hardcoded to fetch the named channel from `zddc.varasys.io/releases/`. Drop one over a deployment root to switch the whole site to that channel.
|
|
|
|
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
|
|
|
|
## Training-data
|
|
|
|
The `training-data/` directory scripts and README are committed. The data itself is excluded via `training-data/.gitignore`:
|
|
|
|
- `raw/` — raw interaction logs (never committed)
|
|
- `processed/`, `validation/`, `adapters/`, `snapshots/` — generated data (never committed)
|
|
|
|
## 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-Email` | Header set by upstream proxy with user email |
|
|
| `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
|
|
|
|
```sh
|
|
git tag zddc-server-v1.0.0
|
|
git push --tags
|
|
```
|
|
|
|
### 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
|
|
- The `.archive` virtual path resolves ZDDC tracking numbers to their earliest-received revision
|
|
- ACL is enforced via cascading `.zddc` YAML files; authentication is delegated to the upstream proxy via the `X-Email` header
|