feat(web): downloads page with OCI pull instructions and version listings (BUNYIP-34) #36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/bunyip-34-downloads-oci-ux"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Implements BUNYIP-34: the downloads page becomes a complete distribution UX covering both binary downloads and OCI images, with copy-paste docker commands.
API changes
/v1/downloadsreturns one group per active product with ANY distribution surface (binary assets, OCI image, or both). Previously OCI-only catalog products (mokosh-server/api/www) had no presence on the page at all.AppOciImagemodel: registry host (OCI_REGISTRY_SERVICE), repository (= app slug), pinned tag, and the full pull reference.Web changes
docker login <registry>/docker pull <reference>commands plus copy-to-clipboard buttons.Version listing policy
The pinned version per product is the only version the registry/proxy actually serves (pinned-version distribution model), so it is what gets listed and marked Latest. Listing historical versions would advertise versions members cannot pull.
Known limitation (pre-existing)
/v1/downloadsrequires member access, so non-members see "No downloads available" rather than a catalog teaser with upgrade prompts. Unchanged from current behavior; worth its own issue if a marketing teaser is wanted.Verification (live dev stack)
localhost:18081/...references), vervain-agent binary assets/v1/downloadsJSONociblock, binary products carry assets +release_tagNote: requires PR #35 (BUNYIP-37 dev container fix) for local web development, but has no code dependency on it.
- Wire compatibility: AppDownloadGroup.release_tag stays a plain string (empty for OCI-only products) instead of Option. An Option serialized as null would fail deserialization in already-deployed web clients and blank the whole downloads page during any api-before-web deploy window; the empty-string form parses on both old and new clients in both directions. - Dead entries: a product whose Forgejo release exists but has zero uploaded assets (and no pullable image) is now excluded from /v1/downloads instead of rendering a header + Latest badge with nothing actionable. The guard is now "no assets AND no image -> skip". - Performance: /v1/downloads builds all product groups concurrently via join_all (order preserved), so cold-cache page latency is one Forgejo round-trip instead of the sum of N sequential ones. Per-product logic extracted to build_download_group(). - Altitude: the pull-reference format ({registry}/{slug}:{tag}) moves to Application::oci_pull_image() in bunyip-domain, next to is_pullable()/download_source(). The slug-as-repository convention now has a single definition that the registry routes, the token scope check, and the downloads page all align on; the handler-local oci_pull_info() and its tests are replaced by domain tests. - Error state: the web downloads page distinguishes an API error (error box, "temporarily unavailable") from a genuinely empty catalog ("No downloads available") instead of collapsing both into the empty state. - Copy button UX: clipboard writes now report success/failure on the button label, and when the Clipboard API is unavailable (plain-HTTP self-host deployments are not secure contexts) the command text is selected for manual copy instead of silently throwing. - Cleanup: dead `?` after the is_pullable() guarantee replaced with an expect() documenting the invariant; infallible serde_json::to_string unwrap_or_default (whose fallback would render a broken onclick) replaced with expect(); duplicated "Upgrade to access" markup extracted to upgrade_link(); admin_refresh_release moved above the test module, permanently fixing the pre-existing items-after-test-module clippy warning. Verified: fmt clean; clippy 46 warnings vs main's 47 (zero new, one pre-existing fixed); api/domain tests pass; live dev stack confirms release_tag is a plain string (empty for OCI-only), no dead entries, 8 copy buttons with feedback JS + fallback, error/empty states distinct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>