Files
Evan Jarrett ab4a4ebf9d admin panel long running imrovements, billing fixes, ui cleanup
1. Multiple registry domains + per-user domain preference

The biggest feature. The appview can serve several registry domains (e.g. buoy.cr, atcr.io),
and users can now pick which one shows up in their pull/push commands.

- Lexicon/record: adds registryDomain (and documents ociClient)
to the sailor profile (lexicons/.../profile.json, pkg/atproto/lexicon.go).
- DB: new registry_domain column on users (schema.sql + migration 0027),
with GetUserByDID/Handle reads, UpdateUserRegistryDomain writer,
and Jetstream caching it on profile updates (writes unconditionally so clearing propagates).
- UI/handlers: new UpdateRegistryDomainHandler + /api/profile/registry-domain route,
a <select> in the user settings panel (only shown when >1 domain configured), and resolveRegistryURL()
which falls back to the primary domain if the user's pref is stale/removed. Tests added for all of it.

2. default_hold_did removed → first managed_holds entry is the default

Consolidates two overlapping config fields into one. ServerConfig.DefaultHoldDID is gone;
PrimaryHoldDID() now returns managed_holds[0]. managed_holds is now REQUIRED.
Updated in config, validation, server wiring, test harness, example YAML, and the deploy template.

3. Admin long-running operations → generic background-job framework

New pkg/hold/admin/jobs.go introduces a reusable startJob/jobRegistry pattern
 (a detached context.Background() job + a /admin/api/jobs/{key}/status polling endpoint).
This replaces the bespoke scan-backfill goroutine state machine, and now also wraps crew tier remap and crew import
all three previously looped synchronously on the request context and got 504'd/cancelled mid-run by the reverse proxy.
 Forms switched from POST-redirect to htmx fragments (job_progress.html, job_result.html, crew_import_results.html)
 the old crew_import_results.html page and scan_backfill_progress.html partial were deleted.
This is also captured as a new rule in CLAUDE.md.

4. Cascade-delete manifest on last-tag deletion

DeleteTagHandler now, after removing the last tag pointing to a digest, cascade-deletes the manifest itself
 (PDS + DB + hold blob purge) — but only if it's not a child of a manifest list (multi-arch parent).
 New GetTagDigest and ShouldCascadeDeleteManifest queries back it, plus cascade_delete_test.go.
 Also switches tag rkey computation to the atproto.RepositoryTagToRKey helper.

5. Billing simplification

Drops the OwnerBadge config option (hold-owner supporter badge).
The user-profile template no longer special-cases an "owner" badge value (only "Captain").
Example tiers renamed to the nautical scheme (deckhand/bosun/quartermaster).

6. Build/deploy: go generate always runs via Make

make generate is now a phony target that always runs go generate ./... (regenerating cbor_gen, icon sprites, etc.),
 and build-trixie depends on it. The deploy tooling (provision.go/update.go)
drops its own runGenerate calls since the Makefile handles it.

7. New cmd/firehose-tap tool (untracked)

A standalone CLI that subscribes to a com.atproto.sync.subscribeRepos endpoint and pretty-prints events,
with emphasis on Sync 1.1 compliance fields (per-op prev CIDs, commit prevData) and a --validate CI mode.
Fits with the recent "more sync1.1 compliant" commit.
2026-06-05 20:57:25 -05:00

75 lines
3.9 KiB
HTML

{{ define "settings-panel-user" }}
<section class="card bg-base-200 shadow-sm p-6 space-y-6">
<div>
<h2 class="text-xl font-semibold">Preferences</h2>
<p class="text-base-content/70 mt-1">Customize your experience across the site.</p>
</div>
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
<div class="sm:flex-1 sm:min-w-0">
<label for="oci-client-select" class="text-sm font-medium">Preferred client</label>
<p id="oci-client-hint" class="text-xs text-base-content/70">Sets the pull command shown on repository pages. Choose <em>Image reference only</em> to copy without a command prefix.</p>
</div>
{{ $oci := .Profile.OciClient }}
<select id="oci-client-select" aria-describedby="oci-client-hint" class="select select-sm select-bordered w-full sm:w-56 sm:shrink-0"
name="oci_client"
hx-post="/api/profile/oci-client"
hx-trigger="change"
hx-swap="none">
<option value="docker"{{ if or (eq $oci "") (eq $oci "docker") }} selected{{ end }}>Docker</option>
<option value="podman"{{ if eq $oci "podman" }} selected{{ end }}>Podman</option>
<option value="buildah"{{ if eq $oci "buildah" }} selected{{ end }}>Buildah</option>
<option value="nerdctl"{{ if eq $oci "nerdctl" }} selected{{ end }}>nerdctl</option>
<option value="crane"{{ if eq $oci "crane" }} selected{{ end }}>crane</option>
<option value="none"{{ if eq $oci "none" }} selected{{ end }}>Image reference only</option>
</select>
</div>
{{ if gt (len .RegistryDomains) 1 }}
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
<div class="sm:flex-1 sm:min-w-0">
<label for="registry-domain-select" class="text-sm font-medium">Registry domain</label>
<p id="registry-domain-hint" class="text-xs text-base-content/70">Sets the registry domain shown in pull and push commands across the site. All domains work, this only changes what is displayed.</p>
</div>
{{ $pref := .Profile.RegistryDomain }}
<select id="registry-domain-select" aria-describedby="registry-domain-hint" class="select select-sm select-bordered w-full sm:w-56 sm:shrink-0"
name="registry_domain"
hx-post="/api/profile/registry-domain"
hx-trigger="change"
hx-swap="none">
{{ range $i, $d := .RegistryDomains }}
<option value="{{ $d }}"{{ if or (eq $d $pref) (and (eq $i 0) (eq $pref "")) }} selected{{ end }}>{{ $d }}</option>
{{ end }}
</select>
</div>
{{ end }}
{{ if .AIAdvisorEnabled }}
<div class="divider my-2"></div>
<div class="flex items-start gap-3">
{{ if .Profile.HasAIAdvisorAccess }}
<label class="flex items-start gap-3 cursor-pointer">
<input type="checkbox" class="toggle toggle-primary mt-0.5"
hx-post="/api/profile/ai-advisor"
hx-trigger="change"
hx-swap="none"
{{ if .Profile.AIAdvisorEnabled }}checked{{ end }}>
<div>
<span class="font-medium">AI Image Advisor</span>
<p class="text-xs text-base-content/60">Analyze your container images for optimization suggestions using AI.</p>
</div>
</label>
{{ else }}
<div>
<span class="font-medium text-base-content/50">AI Image Advisor</span>
<p class="text-xs text-base-content/70">Analyze your container images for optimization suggestions using AI.</p>
<p class="text-xs text-base-content/70 mt-1">
<a href="/settings/billing" class="text-primary link link-hover">Upgrade your plan</a> to enable this feature.
</p>
</div>
{{ end }}
</div>
{{ end }}
</section>
{{ end }}