feat(web): downloads page with OCI pull instructions and version listings (BUNYIP-34) #36

Merged
nrupard merged 2 commits from feat/bunyip-34-downloads-oci-ux into main 2026-06-02 18:30:15 +02:00
Owner

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/downloads returns 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.
  • New AppOciImage model: registry host (OCI_REGISTRY_SERVICE), repository (= app slug), pinned tag, and the full pull reference.
  • The endpoint 404s only when BOTH the download proxy and the OCI registry are disabled server-side; a failed Forgejo release fetch degrades a product to its OCI block instead of dropping it.

Web changes

  • Per-product sections: pinned version badged as Latest, binary asset list (existing), and a Container image block with docker login <registry> / docker pull <reference> commands plus copy-to-clipboard buttons.
  • Login UX: the block tells members to authenticate with their Bunyip account email and password (matching the registry's basic-auth token flow).
  • Commands are JSON-encoded into the onclick handler so they are valid JS string literals after Maud's HTML escaping.

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/downloads requires 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)

Check Result
Member /downloads page All 5 products render: 4 OCI blocks (correct localhost:18081/... references), vervain-agent binary assets
Copy buttons 8 (2 commands x 4 OCI products)
Latest badges 5 (one per product)
Non-member / unauthenticated Renders sensibly (no errors)
/v1/downloads JSON OCI products carry oci block, binary products carry assets + release_tag
Workspace checks fmt clean, zero new clippy violations (one pre-existing warning removed), 3 new unit tests pass

Note: requires PR #35 (BUNYIP-37 dev container fix) for local web development, but has no code dependency on it.

## 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/downloads` returns 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. * New `AppOciImage` model: registry host (`OCI_REGISTRY_SERVICE`), repository (= app slug), pinned tag, and the full pull reference. * The endpoint 404s only when BOTH the download proxy and the OCI registry are disabled server-side; a failed Forgejo release fetch degrades a product to its OCI block instead of dropping it. ## Web changes * Per-product sections: pinned version badged as **Latest**, binary asset list (existing), and a **Container image** block with `docker login <registry>` / `docker pull <reference>` commands plus copy-to-clipboard buttons. * Login UX: the block tells members to authenticate with their Bunyip account email and password (matching the registry's basic-auth token flow). * Commands are JSON-encoded into the onclick handler so they are valid JS string literals after Maud's HTML escaping. ## 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/downloads` requires 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) | Check | Result | | --- | --- | | Member /downloads page | All 5 products render: 4 OCI blocks (correct `localhost:18081/...` references), vervain-agent binary assets | | Copy buttons | 8 (2 commands x 4 OCI products) | | Latest badges | 5 (one per product) | | Non-member / unauthenticated | Renders sensibly (no errors) | | `/v1/downloads` JSON | OCI products carry `oci` block, binary products carry assets + `release_tag` | | Workspace checks | fmt clean, zero new clippy violations (one pre-existing warning removed), 3 new unit tests pass | Note: requires PR #35 (BUNYIP-37 dev container fix) for local web development, but has no code dependency on it.
feat(web): downloads page with OCI pull instructions and version badges (BUNYIP-34)
Some checks failed
Check / fmt / clippy / build / test (pull_request) Failing after 17s
b69c8a43d5
API side: /v1/downloads now returns one group per active product with ANY distribution surface (binary assets, OCI image, or both) instead of only release-configured apps. New AppOciImage model carries the member-facing pull coordinates (registry host from OCI_REGISTRY_SERVICE, repository = slug, pinned tag, full reference); oci_pull_info() builds it only when the registry is enabled and the app is pullable. The endpoint 404s only when BOTH the download proxy and the OCI registry are disabled, and a failed Forgejo release fetch degrades that product to its OCI block instead of dropping it from the page.

Web side: the downloads page renders per-product sections with the pinned version marked as Latest, the existing binary asset list, and a new Container image block with copy-paste docker login / docker pull commands (copy-to-clipboard buttons; commands JSON-encoded into the onclick handler so they are safe JS string literals). The login hint tells members to authenticate with their account email and password.

Versions: the pinned version per product is the only version the registry/proxy serves (pinned-version distribution model), so it is listed and badged as Latest; historical versions are not pullable and therefore not listed.

Known limitation (pre-existing): /v1/downloads requires member access, so non-members see "No downloads available" rather than a catalog teaser with upgrade prompts.

Verified live on the dev stack: member sees all 5 products (4 OCI blocks with correct localhost:18081 references, vervain-agent binary assets), 8 copy buttons, 5 Latest badges; non-member and unauthenticated flows render sensibly; workspace fmt/clippy/tests clean (3 new unit tests for oci_pull_info, zero new clippy violations, one pre-existing warning removed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(downloads): address PR #36 review findings
Some checks failed
Check / fmt / clippy / build / test (pull_request) Failing after 27s
Create release / Create release from merged PR (pull_request) Has been skipped
26fcec5719
- 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>
nrupard deleted branch feat/bunyip-34-downloads-oci-ux 2026-06-02 18:30:15 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/bunyip!36
No description provided.