ZDDC/website/releases/index.html
ZDDC 9fce18cd45 feat: lockstep release infra + cascade/.archive fixes + profile perf + page redesign
Four entangled change-sets from one session, committed together because
their file-level overlap (build.sh, docs, embedded/, watcher.go, …) makes
post-hoc separation noisy:

* fix(archive): nested-party + folder-type cascade
  transmittalIsUnderVisibleParty short-circuited on the first matched
  party segment, only checking the immediately-next segment for a
  folder-type marker. Paths like BM/sub/Issued/<txn> bypassed the Issued
  toggle entirely. Replaced with isUnderHiddenFolderType (full-path) +
  any-segment party match. Eight new Playwright cases pin the contract
  in tests/archive-cascade.spec.js.

* refactor(zddc-server): scope .archive index by project
  archive.Index now buckets by top-level segment
  (.ByProject[<project>].ByTracking[<tracking>]). Resolve and AllEntries
  take a project parameter; handler extracts it from contextPath's first
  segment. /.archive/ at root returns 404 — stable refs must be
  project-rooted. Within-project (tracking, rev) collisions emit a WARN
  with both paths. Cross-project tracking-number duplicates no longer
  collide.

* perf(zddc-server): lazy-load expensive bits of the profile page
  serveProfilePage now ships a minimal shell: Email, EmailHeader,
  IsSuperAdmin (root .zddc only). Visible projects + admin subtrees +
  editable scaffolds populate client-side via /.profile/access. Subtree-
  admin scaffolds live in <template id="tmpl-subtree-admin">; pure
  non-admins receive no live admin form. ScanZddcFiles now memoized,
  invalidated on .zddc events by the watcher and writer helpers.

* feat: lockstep release + redesigned releases page
  sh build.sh --release [version|alpha|beta] is the canonical lockstep
  cut: every tool (5 HTML + zddc-server) bumps to the same coordinated
  version. zddc-server binaries now committed under website/releases/
  with the same cascade chain as HTML tools (no more Codeberg release-
  asset publication). zddc/release.sh deprecated (kept as a guard);
  shared/publish-codeberg-release.sh removed.

  Releases page redesigned as an action-first install guide: hero +
  version dropdown that rewires every download link, channel chips for
  always-visible alpha/beta access (state-aware labels: "tracks stable"
  vs "active dev"), Path A (zddc-server with platform auto-detect from
  UA), Path B (5 standalone tool HTMLs), version-pinning empowerment
  narrative (drop-a-copy vs .zddc apps: cascade), channels explainer.

  Channel-link verifier asserts every <tool>_{stable,beta,alpha}.html
  resolves at the end of every build. Bootstrap-friendly: zddc-server
  artifact checks skip until the first lockstep cut anchors the chain.

Tests: 167 Playwright + all Go packages green.
Docs: CLAUDE.md, AGENTS.md, ARCHITECTURE.md, zddc/README.md updated.

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

275 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Download ZDDC</title>
<meta name="description" content="Self-host the ZDDC server, or download individual tools. Pin a version your project trusts; your archive's tools are yours.">
<meta name="theme-color" content="#2a5a8a">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<header class="site-header">
<div class="container header-content">
<a href="/" class="brand">
<svg class="brand-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#1e3a5f"/>
<g fill="#fff">
<rect x="14" y="18" width="36" height="7"/>
<polygon points="43,25 50,25 21,43 14,43"/>
<rect x="14" y="43" width="36" height="7"/>
</g>
</svg>
<span class="brand-name">ZDDC</span>
</a>
<nav class="header-nav">
<a href="/" class="nav-link">Home</a>
<a href="../reference.html" class="nav-link">Docs</a>
<a href="index.html" class="nav-link active">Download</a>
</nav>
</div>
</header>
<section class="hero">
<div class="container">
<h1>Download ZDDC</h1>
<p class="hero-subtitle">Pick how you want to use it. Pick the version you want. Every link below points at a real, immutable file you can save into your archive — your tools, your version, forever.</p>
</div>
</section>
<main class="container" style="margin-bottom: var(--spacing-2xl);">
<div class="version-picker-bar">
<label for="version-picker">Showing</label>
<select id="version-picker">
<option value="v0.0.2" selected>v0.0.2 (current stable)</option>
<option value="v0.0.1">v0.0.1</option>
<optgroup label="Pre-release channels">
<option value="beta">beta — currently tracks stable</option>
<option value="alpha">alpha — currently tracks stable</option>
</optgroup>
</select>
<span class="picker-hint">Changes every download link below.</span>
</div>
<!-- Channel quick-pick chips — visible at-a-glance entry points to
alpha and beta even when they cascade to stable. Clicking drives
the version picker (rewires the page) rather than navigating
away. The chip is always live; the cascade rule keeps the URLs
it represents permanently resolvable. -->
<div class="channel-chips" role="group" aria-label="Channel quick pick">
<span class="channel-chips-label">Or pick a channel:</span>
<button type="button" class="channel-chip is-current" data-channel="stable">stable</button>
<button type="button" class="channel-chip" data-channel="beta">beta</button>
<button type="button" class="channel-chip" data-channel="alpha">alpha</button>
</div>
<!-- ───────────── Path A — Self-host the server ───────────── -->
<section class="card" style="background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-lg);">
<h2 style="margin-top:0;">Path A — Self-host the server</h2>
<p>One small Go binary. <strong>All five tools are baked in</strong> via <code>//go:embed</code>; the server picks the right one for each folder of your archive. Adds ACL via <code>.zddc</code> files, the virtual <code>.archive</code> document index, and SSO header passthrough. Stop the server and the directory is still a perfectly valid ZDDC archive — the server is convenience, not lock-in.</p>
<p style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: var(--color-bg); border-left: 3px solid var(--color-accent); border-radius: var(--radius-sm); color: var(--color-text);">
<strong>Not yet published.</strong> The first lockstep release publishes binaries here. Until then, build from source: <code>git clone</code> and <code>(cd zddc && go build ./cmd/zddc-server)</code>. Once <code>sh build.sh --release</code> runs, this card auto-populates with download buttons for every platform.
</p>
</section>
<!-- ───────────── Path B — Standalone tool HTMLs ───────────── -->
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl);">
<h2 style="margin-top:0;">Path B — Standalone tools</h2>
<p>Every tool is a single self-contained HTML file. <strong>Open it locally and point it at a folder on your disk</strong> — no install, no server, no account. Same on-disk layout the server uses. Use one tool, use all five, mix and match — there is no orchestration to set up.</p>
<div class="grid-4" style="margin-top: var(--spacing-md);">
<a class="tool-card" data-tool="archive" href="archive_v0.0.2.html">
<span class="tool-card__title">Archive Browser</span>
<span class="tool-card__desc">Browse and download from a ZDDC archive.</span>
<span class="tool-card__link">Download &rarr;</span>
</a>
<a class="tool-card" data-tool="transmittal" href="transmittal_v0.0.2.html">
<span class="tool-card__title">Transmittal Creator</span>
<span class="tool-card__desc">Build, sign, and verify transmittal packages.</span>
<span class="tool-card__link">Download &rarr;</span>
</a>
<a class="tool-card" data-tool="classifier" href="classifier_v0.0.2.html">
<span class="tool-card__title">Classifier</span>
<span class="tool-card__desc">Rename loose files to ZDDC convention.</span>
<span class="tool-card__link">Download &rarr;</span>
</a>
<a class="tool-card" data-tool="mdedit" href="mdedit_v0.0.2.html">
<span class="tool-card__title">Markdown Editor</span>
<span class="tool-card__desc">Edit project markdown files in place.</span>
<span class="tool-card__link">Download &rarr;</span>
</a>
<a class="tool-card" data-tool="landing" href="landing_v0.0.2.html">
<span class="tool-card__title">Landing</span>
<span class="tool-card__desc">Project picker for multi-project servers.</span>
<span class="tool-card__link">Download &rarr;</span>
</a>
</div>
</section>
<!-- ───────────── Pinning empowerment narrative ───────────── -->
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl);">
<h2 style="margin-top:0;">Your version, forever</h2>
<p>Your server may run v0.0.8 next month and v0.1.0 the month after. <strong>Your project doesn't have to follow.</strong> If you depend on a specific behavior in <code>archive</code> v0.0.5, save that version into your archive — the next server upgrade can't take it away from you. Two ways to do it:</p>
<div class="grid-2" style="margin-top: var(--spacing-md);">
<div class="pin-card">
<h3>Drop a copy into your archive</h3>
<p>Save the tool's HTML at the path the server would serve it from. The server's resolution order picks up real files <em>first</em> — before any cascade or embedded fallback.</p>
<pre>curl -o MyProject/archive.html \
https://zddc.varasys.io/releases/archive_v0.0.2.html</pre>
<p>Now <code>MyProject/archive.html</code> is yours. The server serves your bytes; nothing about a future <code>--release</code> can change them.</p>
</div>
<div class="pin-card">
<h3>Pin via <code>.zddc</code></h3>
<p>Less invasive — no copies in your archive, just a small config entry telling the server which version to fetch and cache. Closer-to-leaf wins, so subprojects can pin further.</p>
<pre># MyProject/.zddc
apps:
archive: v0.0.2</pre>
<p>Server fetches once on first hit, caches under <code>_app/</code>, falls through to the embedded copy if the fetch fails.</p>
</div>
</div>
<p class="pin-note">Your archive's tools are <strong>yours</strong>. The server is convenience; deletion of the server doesn't break your archive — every per-version download above is a real, immutable static file. Save what you trust.</p>
</section>
<!-- ───────────── Channels explainer ───────────── -->
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl); margin-bottom: var(--spacing-xl);">
<h2 style="margin-top:0;">Channels</h2>
<p>Three channels, applied in lockstep across all tools. Pre-release channels exist to soak changes; <strong>stable</strong> is what production runs.</p>
<div class="channel-explainer">
<div>
<h4 class="alpha">alpha</h4>
<p>Active dev iteration. Rebuilds without notice. Look here for the very latest.</p>
</div>
<div>
<h4 class="beta">beta</h4>
<p>Ready for general testing. Has soaked through alpha. Still mutable — pin to a versioned URL for reproducibility.</p>
</div>
<div>
<h4 class="stable">stable</h4>
<p>Ready to ship. Every per-version file is immutable; <code>_stable</code> follows the latest cut. Channel cuts cascade: stable cut resets beta and alpha to track stable.</p>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-content">
<span>ZDDC is open source — <a href="https://codeberg.org/VARASYS/ZDDC">codeberg.org/VARASYS/ZDDC</a></span>
</div>
</footer>
<script>
(function() {
// Platform auto-detect: choose the most likely binary for this user's
// OS on first paint. Promotes that platform to the primary CTA; the
// other three render in the secondary row. UA-sniffing is good
// enough — wrong guesses fall through to the always-visible
// "Other platforms" row below.
var ua = navigator.userAgent || '';
var detected = 'linux-amd64'; // sensible default
var platLabel = 'Linux (x86_64)';
if (/Macintosh|Mac OS X/.test(ua)) {
// Apple Silicon vs Intel — UA hints aren't reliable, prefer arm64
// since modern Macs are predominantly arm64. Users on Intel can
// pick from "Other platforms".
detected = 'darwin-arm64';
platLabel = 'macOS (Apple Silicon)';
} else if (/Windows/.test(ua)) {
detected = 'windows-amd64';
platLabel = 'Windows (x86_64)';
}
var primary = document.getElementById('dl-primary-binary');
var primaryLabel = document.getElementById('dl-primary-platlabel');
var primaryMeta = document.getElementById('dl-primary-meta');
var others = document.getElementById('dl-others');
function platBinaryName(version, plat) {
var suf = (plat.indexOf('windows') === 0) ? '.exe' : '';
return 'zddc-server_' + version + '_' + plat + suf;
}
function htmlAssetName(tool, version) {
return tool + '_' + version + '.html';
}
// Update the primary button to the detected platform (if different
// from default). Hide the matching link in the secondary row to
// avoid duplication.
if (primary) {
var initialVer = primary.getAttribute('href').match(/_v[\d.]+_/);
initialVer = initialVer ? initialVer[0].slice(2, -1) : null;
primary.dataset.platform = detected;
if (initialVer) {
primary.href = platBinaryName('v' + initialVer, detected);
if (primaryMeta) primaryMeta.textContent = platBinaryName('v' + initialVer, detected);
}
if (primaryLabel) primaryLabel.textContent = 'for ' + platLabel;
}
// Hide the duplicate in "Other platforms" row.
if (others) {
others.querySelectorAll('a[data-platform="' + detected + '"]').forEach(function(a) {
a.style.display = 'none';
});
}
// Wire the version picker + channel chips. Both drive the same
// rewire function. Selecting a per-version stable from the picker
// also updates the chip set: stable becomes active, beta/alpha lose
// their is-current marker.
var picker = document.getElementById('version-picker');
if (!picker) return;
var chips = document.querySelectorAll('.channel-chip');
function rewire(v) {
// v is "vX.Y.Z" or "alpha" / "beta"
document.querySelectorAll('[data-tool]').forEach(function(a) {
var tool = a.dataset.tool;
var plat = a.dataset.platform || '';
if (tool === 'zddc-server') {
if (plat) {
a.href = (v === 'alpha' || v === 'beta')
? 'zddc-server_' + v + '_' + plat + (plat.indexOf('windows') === 0 ? '.exe' : '')
: platBinaryName(v, plat);
} else {
a.href = 'zddc-server_' + v + '.html';
}
} else {
a.href = htmlAssetName(tool, v);
}
});
if (primary && primaryMeta) {
primaryMeta.textContent = primary.getAttribute('href');
}
// Reflect channel-vs-version in the chip group. Per-version stable
// selections highlight the "stable" chip (since per-version files
// are stable releases).
var channel = (v === 'alpha' || v === 'beta') ? v : 'stable';
chips.forEach(function(c) {
c.classList.toggle('is-current', c.dataset.channel === channel);
});
}
picker.addEventListener('change', function() { rewire(picker.value); });
// Channel chips: clicking sets the picker's value to the channel
// (or to the latest stable when stable is clicked) and fires a
// change event so rewire() runs through the existing flow.
chips.forEach(function(c) {
c.addEventListener('click', function() {
var ch = c.dataset.channel;
if (ch === 'stable') {
// Latest stable is the first non-channel <option> in the picker.
var firstStable = picker.querySelector('option[value^="v"]');
if (firstStable) picker.value = firstStable.value;
} else {
picker.value = ch;
}
rewire(picker.value);
});
});
})();
</script>
</body>
</html>