···5555# Default: AT Container Registry
5656# ATCR_CLIENT_NAME=AT Container Registry
57575858+# Short brand name for page titles and metadata
5959+# Used in meta tags, page titles, and UI text
6060+# Default: ATCR
6161+# ATCR_CLIENT_SHORT_NAME=ATCR
6262+5863# ==============================================================================
5964# UI Configuration
6065# ==============================================================================
+5
.env.example
···7878# Default: AT Container Registry
7979# ATCR_CLIENT_NAME=AT Container Registry
80808181+# Short brand name for page titles and metadata
8282+# Used in meta tags, page titles, and UI text
8383+# Default: ATCR
8484+# ATCR_CLIENT_SHORT_NAME=ATCR
8585+8186# ==============================================================================
8287# APPVIEW - WEB UI
8388# ==============================================================================
···144144# Default: AT Container Registry
145145# ATCR_CLIENT_NAME=AT Container Registry
146146147147+# Short brand name for page titles and metadata
148148+# Used in meta tags, page titles, and UI text
149149+# Default: ATCR
150150+# ATCR_CLIENT_SHORT_NAME=ATCR
151151+147152# ==============================================================================
148153# Legal Page Customization
149154# ==============================================================================
···11-# CSS/JS Minification for ATCR
22-33-## Overview
44-55-ATCR embeds static assets (CSS, JavaScript) directly into the binary using Go's `embed` directive. Currently:
66-77-- **CSS Size:** 40KB (`pkg/appview/public/css/style.css`, 2,210 lines)
88-- **Embedded:** All static files compiled into binary at build time
99-- **No Minification:** Source files embedded as-is
1010-1111-**Problem:** Embedded assets increase binary size and network transfer time.
1212-1313-**Solution:** Minify CSS/JS before embedding to reduce both binary size and network transfer.
1414-1515-## Recommended Approach: `tdewolff/minify`
1616-1717-Use the pure Go `tdewolff/minify` library with `go:generate` to minify assets at build time.
1818-1919-**Benefits:**
2020-- Pure Go, no external dependencies (Node.js, npm)
2121-- Integrates with existing `go:generate` workflow
2222-- ~30-40% CSS size reduction (40KB → ~28KB)
2323-- Minifies CSS, JS, HTML, JSON, SVG, XML
2424-2525-## Implementation
2626-2727-### Step 1: Add Dependency
2828-2929-```bash
3030-go get github.com/tdewolff/minify/v2
3131-```
3232-3333-This will update `go.mod`:
3434-```go
3535-require github.com/tdewolff/minify/v2 v2.20.37
3636-```
3737-3838-### Step 2: Create Minification Script
3939-4040-Create `pkg/appview/public/minify_assets.go`:
4141-4242-```go
4343-//go:build ignore
4444-4545-package main
4646-4747-import (
4848- "fmt"
4949- "log"
5050- "os"
5151- "path/filepath"
5252-5353- "github.com/tdewolff/minify/v2"
5454- "github.com/tdewolff/minify/v2/css"
5555- "github.com/tdewolff/minify/v2/js"
5656-)
5757-5858-func main() {
5959- m := minify.New()
6060- m.AddFunc("text/css", css.Minify)
6161- m.AddFunc("text/javascript", js.Minify)
6262-6363- // Get the directory of this script
6464- dir, err := os.Getwd()
6565- if err != nil {
6666- log.Fatal(err)
6767- }
6868-6969- // Minify CSS
7070- if err := minifyFile(m, "text/css",
7171- filepath.Join(dir, "pkg/appview/public/css/style.css"),
7272- filepath.Join(dir, "pkg/appview/public/css/style.min.css"),
7373- ); err != nil {
7474- log.Fatalf("Failed to minify CSS: %v", err)
7575- }
7676-7777- // Minify JavaScript
7878- if err := minifyFile(m, "text/javascript",
7979- filepath.Join(dir, "pkg/appview/public/js/app.js"),
8080- filepath.Join(dir, "pkg/appview/public/js/app.min.js"),
8181- ); err != nil {
8282- log.Fatalf("Failed to minify JS: %v", err)
8383- }
8484-8585- fmt.Println("✓ Assets minified successfully")
8686-}
8787-8888-func minifyFile(m *minify.M, mediatype, src, dst string) error {
8989- // Read source file
9090- input, err := os.ReadFile(src)
9191- if err != nil {
9292- return fmt.Errorf("read %s: %w", src, err)
9393- }
9494-9595- // Minify
9696- output, err := m.Bytes(mediatype, input)
9797- if err != nil {
9898- return fmt.Errorf("minify %s: %w", src, err)
9999- }
100100-101101- // Write minified output
102102- if err := os.WriteFile(dst, output, 0644); err != nil {
103103- return fmt.Errorf("write %s: %w", dst, err)
104104- }
105105-106106- // Print size reduction
107107- originalSize := len(input)
108108- minifiedSize := len(output)
109109- reduction := float64(originalSize-minifiedSize) / float64(originalSize) * 100
110110-111111- fmt.Printf(" %s: %d bytes → %d bytes (%.1f%% reduction)\n",
112112- filepath.Base(src), originalSize, minifiedSize, reduction)
113113-114114- return nil
115115-}
116116-```
117117-118118-### Step 3: Add `go:generate` Directive
119119-120120-Add to `pkg/appview/ui.go` (before the `//go:embed` directive):
121121-122122-```go
123123-//go:generate go run ./public/minify_assets.go
124124-125125-//go:embed public
126126-var publicFS embed.FS
127127-```
128128-129129-### Step 4: Update HTML Templates
130130-131131-Update all template files to reference minified assets:
132132-133133-**Before:**
134134-```html
135135-<link rel="stylesheet" href="/public/css/style.css">
136136-<script src="/public/js/app.js"></script>
137137-```
138138-139139-**After:**
140140-```html
141141-<link rel="stylesheet" href="/public/css/style.min.css">
142142-<script src="/public/js/app.min.js"></script>
143143-```
144144-145145-**Files to update:**
146146-- `pkg/appview/templates/components/head.html`
147147-- Any other templates that reference CSS/JS directly
148148-149149-### Step 5: Build Workflow
150150-151151-```bash
152152-# Generate minified assets
153153-go generate ./pkg/appview
154154-155155-# Build binary (embeds minified assets)
156156-go build -o bin/atcr-appview ./cmd/appview
157157-158158-# Or build all
159159-go generate ./...
160160-go build -o bin/atcr-appview ./cmd/appview
161161-go build -o bin/atcr-hold ./cmd/hold
162162-```
163163-164164-### Step 6: Add to .gitignore
165165-166166-Add minified files to `.gitignore` since they're generated:
167167-168168-```
169169-# Generated minified assets
170170-pkg/appview/public/css/*.min.css
171171-pkg/appview/public/js/*.min.js
172172-```
173173-174174-**Alternative:** Commit minified files if you want reproducible builds without running `go generate`.
175175-176176-## Build Modes (Optional Enhancement)
177177-178178-Use build tags to serve unminified assets in development:
179179-180180-**Development (default):**
181181-- Edit `style.css` directly
182182-- No minification, easier debugging
183183-- Faster build times
184184-185185-**Production (with `-tags production`):**
186186-- Use minified assets
187187-- Smaller binary size
188188-- Optimized for deployment
189189-190190-### Implementation with Build Tags
191191-192192-**pkg/appview/ui.go** (development):
193193-```go
194194-//go:build !production
195195-196196-//go:embed static
197197-var publicFS embed.FS
198198-199199-func StylePath() string { return "/public/css/style.css" }
200200-func ScriptPath() string { return "/public/js/app.js" }
201201-```
202202-203203-**pkg/appview/ui_production.go** (production):
204204-```go
205205-//go:build production
206206-207207-//go:generate go run ./public/minify_assets.go
208208-209209-//go:embed static
210210-var publicFS embed.FS
211211-212212-func StylePath() string { return "/public/css/style.min.css" }
213213-func ScriptPath() string { return "/public/js/app.min.js" }
214214-```
215215-216216-**Usage:**
217217-```bash
218218-# Development build (unminified)
219219-go build ./cmd/appview
220220-221221-# Production build (minified)
222222-go generate ./pkg/appview
223223-go build -tags production ./cmd/appview
224224-```
225225-226226-## Alternative Approaches
227227-228228-### Option 2: External Minifier (cssnano, esbuild)
229229-230230-Use Node.js-based minifiers via `go:generate`:
231231-232232-```go
233233-//go:generate sh -c "npx cssnano public/css/style.css public/css/style.min.css"
234234-//go:generate sh -c "npx esbuild public/js/app.js --minify --outfile=public/js/app.min.js"
235235-```
236236-237237-**Pros:**
238238-- Best-in-class minification (potentially better than tdewolff)
239239-- Wide ecosystem of tools
240240-241241-**Cons:**
242242-- Requires Node.js/npm in build environment
243243-- Cross-platform compatibility issues (Windows vs Unix)
244244-- External dependency management
245245-246246-### Option 3: Runtime Gzip Compression
247247-248248-Compress assets at runtime (complementary to minification):
249249-250250-```go
251251-import "github.com/NYTimes/gziphandler"
252252-253253-// Wrap static handler
254254-mux.Handle("/public/", gziphandler.GzipHandler(appview.StaticHandler()))
255255-```
256256-257257-**Pros:**
258258-- Works for all static files (images, fonts)
259259-- ~70-80% size reduction over network
260260-- No build changes needed
261261-262262-**Cons:**
263263-- Doesn't reduce binary size
264264-- Adds runtime CPU cost
265265-- Should be combined with minification for best results
266266-267267-### Option 4: Brotli Compression (Better than Gzip)
268268-269269-```go
270270-import "github.com/andybalholm/brotli"
271271-272272-// Custom handler with brotli
273273-func BrotliHandler(h http.Handler) http.Handler {
274274- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
275275- if !strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
276276- h.ServeHTTP(w, r)
277277- return
278278- }
279279- w.Header().Set("Content-Encoding", "br")
280280- bw := brotli.NewWriterLevel(w, brotli.DefaultCompression)
281281- defer bw.Close()
282282- h.ServeHTTP(&brotliResponseWriter{Writer: bw, ResponseWriter: w}, r)
283283- })
284284-}
285285-```
286286-287287-## Expected Benefits
288288-289289-### File Size Reduction
290290-291291-**Current (unminified):**
292292-- CSS: 40KB
293293-- JS: ~5KB (estimated)
294294-- **Total embedded:** ~45KB
295295-296296-**With Minification:**
297297-- CSS: ~28KB (30% reduction)
298298-- JS: ~3KB (40% reduction)
299299-- **Total embedded:** ~31KB
300300-- **Binary size savings:** ~14KB
301301-302302-**With Minification + Gzip (network transfer):**
303303-- CSS: ~8KB (80% reduction from original)
304304-- JS: ~1.5KB (70% reduction from original)
305305-- **Total transferred:** ~9.5KB
306306-307307-### Performance Impact
308308-309309-- **Build time:** +1-2 seconds (running minifier)
310310-- **Runtime:** No impact (files pre-minified)
311311-- **Network:** 75% less data transferred (with gzip)
312312-- **Browser parsing:** Slightly faster (smaller files)
313313-314314-## Maintenance
315315-316316-### Development Workflow
317317-318318-1. **Edit source files:**
319319- - Modify `pkg/appview/public/css/style.css`
320320- - Modify `pkg/appview/public/js/app.js`
321321-322322-2. **Test locally:**
323323- ```bash
324324- # Development build (unminified)
325325- go run ./cmd/appview serve
326326- ```
327327-328328-3. **Build for production:**
329329- ```bash
330330- # Generate minified assets
331331- go generate ./pkg/appview
332332-333333- # Build binary
334334- go build -o bin/atcr-appview ./cmd/appview
335335- ```
336336-337337-4. **CI/CD:**
338338- ```bash
339339- # In GitHub Actions / CI
340340- go generate ./...
341341- go build ./...
342342- ```
343343-344344-### Troubleshooting
345345-346346-**Q: Minified assets not updating?**
347347-- Delete `*.min.css` and `*.min.js` files
348348-- Run `go generate ./pkg/appview` again
349349-350350-**Q: Build fails with "package not found"?**
351351-- Run `go mod tidy` to download dependencies
352352-353353-**Q: CSS broken after minification?**
354354-- Check for syntax errors in source CSS
355355-- Minifier is strict about valid CSS
356356-357357-## Integration with Existing Build
358358-359359-ATCR already uses `go:generate` for:
360360-- CBOR generation (`pkg/atproto/lexicon.go`)
361361-- License downloads (`pkg/appview/licenses/licenses.go`)
362362-363363-Minification follows the same pattern:
364364-```bash
365365-# Generate all (CBOR, licenses, minified assets)
366366-go generate ./...
367367-368368-# Build all binaries
369369-go build -o bin/atcr-appview ./cmd/appview
370370-go build -o bin/atcr-hold ./cmd/hold
371371-go build -o bin/docker-credential-atcr ./cmd/credential-helper
372372-```
373373-374374-## Recommendation
375375-376376-**For ATCR:**
377377-378378-1. **Immediate:** Implement Option 1 (`tdewolff/minify`)
379379- - Pure Go, no external dependencies
380380- - Integrates with existing `go:generate` workflow
381381- - ~30% size reduction
382382-383383-2. **Future:** Add runtime gzip/brotli compression
384384- - Wrap static handler with compression middleware
385385- - Benefits all static assets
386386- - Standard practice for web servers
387387-388388-3. **Long-term:** Consider build modes (development vs production)
389389- - Use unminified assets in development
390390- - Use minified assets in production builds
391391- - Best developer experience
392392-393393-## References
394394-395395-- [tdewolff/minify](https://github.com/tdewolff/minify) - Go minifier library
396396-- [NYTimes/gziphandler](https://github.com/NYTimes/gziphandler) - Gzip middleware
397397-- [Go embed directive](https://pkg.go.dev/embed) - Embedding static files
398398-- [Go generate](https://go.dev/blog/generate) - Code generation tool
-641
docs/TEST_COVERAGE_GAPS.md
···11-# Test Coverage Gaps
22-33-**Overall Coverage:** 39.0% (improved from 37.7%, +1.3%)
44-55-This document tracks files in the `pkg/` directory that need test coverage, organized by package. Data is based on actual `coverage.out` analysis.
66-77-**Last Updated:** After adding tests for atproto utilities, handlers improvements, and OAuth browser functionality.
88-99-## Recent Achievements 🎯
1010-1111-In this testing session, we achieved:
1212-1313-1. **pkg/appview/handlers** - 2.1% → 19.7% (**+17.6%** 🎉)
1414- - Significant improvement in web handler coverage
1515- - Better test coverage across handler functions
1616-1717-2. **pkg/atproto** - 26.1% → 27.8% (**+1.7%**)
1818- - New test files added:
1919- - directory_test.go (NEW)
2020- - endpoints_test.go (NEW)
2121- - utils_test.go (NEW)
2222- - Improved lexicon tests
2323-2424-3. **pkg/auth/oauth** - 48.3% → 50.7% (**+2.4%**)
2525- - browser_test.go improvements
2626- - Better OAuth flow coverage
2727-2828-4. **Overall improvement** - 37.7% → 39.0% (**+1.3%**)
2929- - Cumulative improvement from baseline: 31.2% → 39.0% (**+7.8%**)
3030-3131-**Note:** pkg/appview/db coverage decreased slightly from 44.8% → 41.2% (-3.6%), likely due to additional untested code paths being tracked in existing test files.
3232-3333-**Next Priority:** Continue with storage blob write operations (proxy_blob_store.go Put/Create/Writer methods)
3434-3535----
3636-3737-Legend:
3838-- ⭐ **Critical Priority** - Core functionality that must be tested
3939-- 🔴 **High Priority** - Important functionality with security/data implications
4040-- 🟡 **Medium Priority** - Supporting functionality
4141-- 🟢 **Low Priority** - Nice-to-have, less critical features
4242-- ✅ **Good Coverage** - Package has >70% coverage
4343-- 📊 **Partial Coverage** - File has some coverage but needs more
4444-- 🎯 **Recently Improved** - Coverage significantly improved in latest update
4545-4646----
4747-4848-## Package Coverage Summary
4949-5050-| Package | Coverage | Status | Priority | Change |
5151-|---------|----------|--------|----------|--------|
5252-| `pkg/hold` | 98.0% | ✅ Excellent | - | - |
5353-| `pkg/s3` | 97.4% | ✅ Excellent | - | - |
5454-| `pkg/appview/licenses` | 93.0% | ✅ Excellent | - | - |
5555-| `pkg/appview` | 81.9% | ✅ Excellent | - | +0.1% |
5656-| `pkg/logging` | 75.0% | ✅ Good | - | - |
5757-| `pkg/auth/token` | 68.8% | 🟡 Good | - | - |
5858-| `pkg/appview/middleware` | 57.8% | 🟡 Good | - | - |
5959-| `pkg/auth` | 55.7% | 🟡 Needs work | Medium | - |
6060-| `pkg/hold/oci` | 51.9% | 🟡 Needs work | Medium | - |
6161-| `pkg/appview/storage` | 51.4% | 🟡 Needs work | **High** | - |
6262-| `pkg/auth/oauth` | 50.7% | 🟡 Needs work | High | 🎯 **+2.4%** |
6363-| `pkg/hold/pds` | 47.2% | 🟡 Needs work | Low | - |
6464-| `pkg/appview/db` | 41.2% | 🟡 Needs work | Medium | 🔴 **-3.6%** |
6565-| `pkg/appview/holdhealth` | 41.0% | 🟡 Needs work | Low | - |
6666-| `pkg/atproto` | 27.8% | 🟡 Needs work | High | 🎯 **+1.7%** |
6767-| `pkg/appview/readme` | 27.2% | 🟡 Needs work | Low | - |
6868-| `pkg/appview/handlers` | 19.7% | 🟡 Needs work | Low | 🎯 **+17.6%** |
6969-| `pkg/appview/jetstream` | 11.6% | 🟡 Needs work | Medium | - |
7070-| `pkg/appview/routes` | 10.4% | 🟡 Needs work | Low | - |
7171-7272-**⚠️ Notes on Coverage Changes:**
7373-7474-Several packages show decreased percentages despite improvements. This is due to:
7575-1. **New test files added** - Coverage now tracks previously untested files
7676-2. **Statement weighting** - Large untested functions (like `Repository()` at 0% in middleware) lower overall package percentage
7777-3. **More comprehensive tracking** - Better coverage analysis reveals gaps that were previously invisible
7878-7979-**Specific file-level improvements (hidden by package averages):**
8080-- `pkg/appview/middleware/auth.go`: 98.8% average (excellent)
8181-- `pkg/appview/middleware/registry.go`: 90.8% average (excellent)
8282-- `pkg/appview/storage/manifest_store.go`: 0% → 85%+ (critical improvement)
8383-- `pkg/atproto/client.go`: 74.8% average (good)
8484-- `pkg/atproto/resolver.go`: 74.5% average (good)
8585-8686-**Key Insight:** Focus on file-level coverage for critical paths rather than package averages, as new comprehensive testing can paradoxically lower package percentages while improving actual test quality.
8787-8888----
8989-9090-## Recently Completed ✅
9191-9292-### ✅ pkg/appview/storage/manifest_store.go (85%+ coverage) - **COMPLETED** 🎉
9393-9494-**Achievement:** Improved from 0% to 85%+ (Critical Priority #1 from previous plan)
9595-9696-**Well-covered functions:**
9797-- `NewManifestStore()` - 100% ✅
9898-- `Exists()` - 100% ✅
9999-- `Get()` - 85.7% ✅
100100-- `Put()` - 75.5% ✅
101101-- `Delete()` - 100% ✅
102102-- `digestToRKey()` - 100% ✅
103103-- `GetLastFetchedHoldDID()` - 100% ✅
104104-- `extractConfigLabels()` - 90.0% ✅
105105-- `resolveDIDToHTTPSEndpoint()` - 100% ✅
106106-107107-**Why This Was Critical:**
108108-- Core OCI manifest operations (store/retrieve/delete)
109109-- ATProto record conversion
110110-- Digest-based addressing
111111-- Essential for registry functionality
112112-113113-**Remaining gaps:**
114114-- `notifyHoldAboutManifest()` - 0% (background notification, less critical)
115115-116116-## Critical Priority: Core Registry Functionality
117117-118118-These components are essential to registry operation and still need coverage.
119119-120120-### ⭐ pkg/appview/storage (51.4% coverage) - **HIGHEST PRIORITY**
121121-122122-**Status:** Manifest operations completed ✅, blob write operations remain critical gap
123123-124124-#### proxy_blob_store.go (Partial coverage) - **HIGHEST PRIORITY** 🎯
125125-126126-**Why Critical:** Handles all blob upload/download operations for the registry
127127-128128-**Well-covered (blob reads and helpers):**
129129-- `NewProxyBlobStore()` - 100% ✅
130130-- `doAuthenticatedRequest()` - 100% ✅
131131-- `getPresignedURL()` - 70% ✅
132132-- `startMultipartUpload()` - 70% ✅
133133-- `getPartUploadInfo()` - 70% ✅
134134-- `completeMultipartUpload()` - 75% ✅
135135-- `abortMultipartUpload()` - 70.6% ✅
136136-- `Get()` - 68.8% ✅
137137-- `Open()` - 62.5% ✅
138138-139139-**Needs improvement:**
140140-- `Stat()` - 26.3% 📊
141141-- `checkReadAccess()` - 25.0% 📊
142142-143143-**Critical gaps (0% coverage):**
144144-- `Put()` - Main upload entry point (CRITICAL)
145145-- `Create()` - Blob creation (CRITICAL)
146146-- `Delete()` - Blob deletion
147147-- `ServeBlob()` - Blob serving
148148-- `Resume()` - Upload resumption
149149-- `checkWriteAccess()` - Write authorization
150150-151151-**Writer interface (0% coverage - CRITICAL for uploads):**
152152-- `Write()` - Write data to multipart upload
153153-- `flushPart()` - Flush buffered part
154154-- `ReadFrom()` - io.ReaderFrom implementation
155155-- `Commit()` - Finalize upload
156156-- `Cancel()` - Cancel upload
157157-- `Close()` - Close writer
158158-- `Size()` - Get written size
159159-- `ID()` - Get upload ID
160160-- `StartedAt()` - Get start time
161161-- `Seek()` - Seek in upload
162162-163163-**Test Scenarios Needed:**
164164-1. Full multipart upload flow: `Put()` → `Create()` → `Write()` → `Commit()`
165165-2. Large blob upload with multiple parts
166166-3. Upload cancellation and cleanup
167167-4. Error handling for failed uploads
168168-5. Upload resumption with `Resume()`
169169-6. Write authorization checks
170170-7. Delete operations
171171-172172-#### routing_repository.go (Partial coverage) - **HIGH PRIORITY**
173173-174174-**Current coverage:**
175175-- `Manifests()` - Returns manifest store (mostly tested via manifest_store tests)
176176-- `Blobs()` - 0% coverage (blob routing logic untested)
177177-- `Repository()` - 0% coverage (wrapper method, lower priority)
178178-179179-**Test Scenarios Needed:**
180180-- Blob routing using cached hold DID (pull scenario)
181181-- Blob routing using discovered hold DID (push scenario)
182182-- Error handling for missing hold
183183-- Hold cache integration
184184-185185-#### crew.go (11.1% coverage) - **MEDIUM PRIORITY**
186186-**Functions:**
187187-- `EnsureCrewMembership()` - 11.1%
188188-- `requestCrewMembership()` - 0%
189189-190190-**Test Scenarios Needed:**
191191-- Valid crew member with permissions
192192-- Crew member without required permission
193193-- Non-member access denial
194194-- Crew membership request flow
195195-196196-#### hold_cache.go (93% coverage) - **EXCELLENT** ✅
197197-198198-**Well-covered:**
199199-- `init()` - 80% ✅
200200-- `GetGlobalHoldCache()` - 100% ✅
201201-- `Set()` - 100% ✅
202202-- `Get()` - 100% ✅
203203-- `Cleanup()` - 100% ✅
204204-205205----
206206-207207-## High Priority: Supporting Infrastructure
208208-209209-### 🔴 pkg/auth/oauth (48.3% coverage, improved from 40.4%)
210210-211211-OAuth implementation has test files but many functions remain untested.
212212-213213-#### client.go - Session Management (Refresher) (Partial coverage)
214214-215215-**Well-covered:**
216216-- `NewRefresher()` - 100% ✅
217217-- `SetUISessionStore()` - 100% ✅
218218-219219-**Critical gaps (0% coverage):**
220220-- `GetSession()` - 0% (CRITICAL - main session retrieval)
221221-- `resumeSession()` - 0% (CRITICAL - session resumption)
222222-- `InvalidateSession()` - 0%
223223-- `GetSessionID()` - 0%
224224-225225-**Test Scenarios Needed:**
226226-- Session retrieval and caching
227227-- Token refresh flow
228228-- Concurrent refresh handling (per-DID locking)
229229-230230-**Note:** Refresher functionality merged into client.go (previously separate refresher.go file)
231231-- Cache expiration
232232-- Error handling for failed refreshes
233233-234234-#### server.go (Partial coverage)
235235-236236-**Well-covered:**
237237-- `NewServer()` - 100% ✅
238238-- `SetRefresher()` - 100% ✅
239239-- `SetUISessionStore()` - 100% ✅
240240-- `SetPostAuthCallback()` - 100% ✅
241241-- `renderRedirectToSettings()` - 80.0% ✅
242242-- `renderError()` - 83.3% ✅
243243-244244-**Critical gaps:**
245245-- `ServeAuthorize()` - 36.8% (needs more coverage)
246246-- `ServeCallback()` - 16.3% (CRITICAL - main OAuth callback handler)
247247-248248-**Test Scenarios Needed:**
249249-- Authorization flow initiation
250250-- Callback handling with valid code
251251-- Error handling for invalid state/code
252252-- DPoP proof validation
253253-- State parameter validation
254254-255255-#### interactive.go (41.7% coverage)
256256-**Function:**
257257-- `InteractiveFlowWithCallback()` - 41.7%
258258-259259-**Test Scenarios Needed:**
260260-- Two-phase callback setup
261261-- Browser interaction flow
262262-- Callback server lifecycle
263263-264264-#### client.go (Excellent coverage) ✅
265265-266266-**Well-covered:**
267267-- `NewApp()` - 100% ✅
268268-- `NewAppWithScopes()` - 100% ✅
269269-- `NewClientConfigWithScopes()` - 80.0% ✅
270270-- `GetConfig()` - 100% ✅
271271-- `StartAuthFlow()` - 75.0% ✅
272272-- `ClientIDWithScopes()` - 75.0% ✅
273273-- `RedirectURI()` - 100% ✅
274274-- `GetDefaultScopes()` - 100% ✅
275275-- `ScopesMatch()` - 100% ✅
276276-277277-**Improved (from previous 0%):**
278278-- `ProcessCallback()` - Improved coverage
279279-- `ResumeSession()` - Improved coverage
280280-- `GetClientApp()` - Improved coverage
281281-- `Directory()` - Improved coverage (directory_test.go added)
282282-283283-#### store.go (Good coverage, some gaps)
284284-285285-**Well-covered:**
286286-- `NewFileStore()` - 100% ✅
287287-- `GetSession()` - 100% ✅
288288-- `SaveSession()` - 100% ✅
289289-290290-**Gaps:**
291291-- `GetDefaultStorePath()` - 30.0%
292292-293293-#### browser.go (Improved coverage) 🎯
294294-**Function:**
295295-- `OpenBrowser()` - Improved coverage (browser_test.go enhanced)
296296-297297-**Note:** Browser interaction testing improved, though full CI testing remains challenging
298298-299299----
300300-301301-### 🔴 pkg/appview/db (41.2% coverage, decreased from 44.8%)
302302-303303-Database layer has test files but many functions remain untested. Coverage decrease likely due to additional code paths being tracked in existing tests.
304304-305305-#### queries.go (0% coverage for most functions)
306306-**Functions:**
307307-- Repository queries
308308-- Star counting
309309-- Pull counting
310310-- Search queries
311311-312312-**Test Scenarios Needed:**
313313-- Repository listing with pagination
314314-- Search functionality
315315-- Aggregation queries
316316-- Error handling
317317-318318-#### session_store.go (0% coverage)
319319-**Functions:**
320320-- Session creation and retrieval
321321-- Session expiration
322322-- Session deletion
323323-324324-**Test Scenarios Needed:**
325325-- Session lifecycle
326326-- Expiration handling
327327-- Cleanup of expired sessions
328328-- Concurrent session access
329329-330330-#### device_store.go (📊 Partial coverage)
331331-**Functions:**
332332-- OAuth device flow storage
333333-- Has test file but many functions still at 0%
334334-335335-**Test Scenarios Needed:**
336336-- User code lookups
337337-- Status updates (pending → approved)
338338-- Expiration handling
339339-- Delete operations
340340-341341-#### hold_store.go (📊 Partial coverage)
342342-**Needs integration tests for cache invalidation**
343343-344344-#### oauth_store.go (📊 Partial coverage)
345345-**Uncovered Functions:**
346346-- `GetAuthRequestInfo()` - 0%
347347-- `DeleteAuthRequestInfo()` - 0%
348348-- `SaveAuthRequestInfo()` - 0%
349349-350350-#### annotations.go (0% coverage)
351351-**Functions:**
352352-- Repository annotations and metadata
353353-354354-#### readonly.go (0% coverage)
355355-**Functions:**
356356-- Read-only database wrapper
357357-358358----
359359-360360-## Medium Priority: Supporting Features
361361-362362-### 🟡 pkg/appview/jetstream (16.7% coverage)
363363-364364-Event processing for real-time updates.
365365-366366-#### worker.go (0% coverage)
367367-**Functions:**
368368-- Jetstream event consumption
369369-- Event routing to handlers
370370-- Repository indexing
371371-372372-#### backfill.go (0% coverage)
373373-**Functions:**
374374-- PDS repository backfilling
375375-- Batch processing
376376-377377-#### processor.go (📊 Partial coverage)
378378-**Needs more comprehensive testing**
379379-380380----
381381-382382-### 🟡 pkg/hold/oci (69.9% coverage)
383383-384384-Multipart upload implementation for hold service. Has good coverage overall but some functions still need tests.
385385-386386-#### xrpc.go (📊 Partial coverage)
387387-**Functions:**
388388-- Multipart upload XRPC endpoints
389389-- Most functions tested, but edge cases need coverage
390390-391391----
392392-393393-### 🟡 pkg/hold/pds (57.8% coverage)
394394-395395-Embedded PDS implementation. Has good test coverage for critical parts, but supporting functions need work.
396396-397397-#### repomgr.go (📊 Partial coverage)
398398-**Many functions still at 0% coverage**
399399-400400-#### profile.go (0% coverage)
401401-**Functions:**
402402-- Sailor profile management
403403-404404-#### layer.go (📊 Partial coverage)
405405-#### auth.go (0% coverage)
406406-#### events.go (📊 Partial coverage)
407407-408408----
409409-410410-### 🟡 pkg/auth (55.8% coverage)
411411-412412-#### hold_local.go (0% coverage)
413413-**Functions:**
414414-- Local hold authorization
415415-416416-#### session.go (0% coverage)
417417-**Functions:**
418418-- Session management
419419-420420-#### hold_remote.go (📊 Partial coverage)
421421-**Needs more edge case testing**
422422-423423----
424424-425425-### 🟡 pkg/appview/readme (Partial coverage)
426426-427427-README rendering for repo page descriptions. The cache.go was removed as README content is now stored in `io.atcr.repo.page` records and synced via Jetstream.
428428-429429-#### fetcher.go (📊 Partial coverage)
430430-- `RenderMarkdown()` - renders repo page description markdown
431431-432432----
433433-434434-### 🟡 pkg/appview/routes (33.3% coverage)
435435-436436-#### routes.go (📊 Partial coverage)
437437-**Needs integration tests for route registration and middleware chains**
438438-439439----
440440-441441-## Low Priority: Web UI and Supporting Features
442442-443443-### 🟢 pkg/appview/handlers (19.7% coverage, improved from 2.1%) 🎯
444444-445445-Web UI handlers. Less critical than core registry functionality but still important for user experience.
446446-447447-**Status:** Significant improvement (+17.6%)! Many handlers now have improved test coverage.
448448-449449-**Improved coverage:**
450450-- Multiple handler functions now have better test coverage
451451-- Common patterns across handlers now tested
452452-453453-**Files with partial coverage:**
454454-- `common.go` (📊)
455455-- `device.go` (📊)
456456-- `auth.go` (📊)
457457-- `repository.go` (📊)
458458-- `search.go` (📊)
459459-- `settings.go` (📊)
460460-- `user.go` (📊)
461461-- `images.go` (📊)
462462-- `home.go` (📊)
463463-- `install.go` (📊)
464464-- `logout.go` (📊)
465465-- `manifest_health.go` (📊)
466466-- `api.go` (📊)
467467-468468-**Note:** While individual files may still show gaps, overall handler package coverage has improved significantly.
469469-470470----
471471-472472-### 🟢 pkg/appview/holdhealth (66.1% coverage)
473473-474474-Hold health checking. Adequate coverage overall.
475475-476476-#### worker.go (📊 Partial coverage)
477477-**Could use more edge case testing**
478478-479479----
480480-481481-### 🟢 pkg/appview/ui.go (0% coverage)
482482-483483-UI initialization and setup. Low priority.
484484-485485----
486486-487487-## Recommended Testing Order
488488-489489-### Phase 1: Critical Infrastructure ✅ **NEARLY COMPLETE** (Target: 45% overall)
490490-491491-**Completed:**
492492-1. ✅ `pkg/appview/middleware/auth.go` - Authentication (0% → 98.8% avg)
493493-2. ✅ `pkg/appview/middleware/registry.go` - Core routing (0% → 90.8% avg)
494494-3. ✅ `pkg/atproto/client.go` - PDS client (0% → 74.8%)
495495-4. ✅ `pkg/atproto/resolver.go` - Identity resolution (0% → 74.5%)
496496-5. ✅ `pkg/appview/storage/manifest_store.go` - Manifest operations (0% → 85%+) **🎉 COMPLETED**
497497-6. ✅ `pkg/appview/storage/profile.go` - Sailor profiles (NEW → 98%+) **🎉 COMPLETED**
498498-499499-**Remaining (HIGHEST PRIORITY):**
500500-7. ⭐⭐⭐ `pkg/appview/storage/proxy_blob_store.go` - Blob write operations **CRITICAL**
501501- - `Put()`, `Create()`, Writer interface (0% → 80%+)
502502- - Essential for docker push operations
503503-8. ⭐ `pkg/appview/storage/routing_repository.go` - Blob routing
504504- - `Blobs()` method (0% → 80%+)
505505-506506-**Current Status:** Overall coverage improved from 37.7% → 39.0% (+1.3%). On track for 45% with Phase 1 completion.
507507-508508-### Phase 2: Supporting Infrastructure (Target: 50% overall)
509509-510510-**In Progress:**
511511-9. 🔴 `pkg/appview/db/*` - Database layer (41.2%, needs improvement)
512512- - queries.go, session_store.go, device_store.go
513513-10. 🔴 `pkg/auth/oauth/client.go` - Session management (Refresher) (Partial → 70%+)
514514- - `GetSession()`, `resumeSession()` (currently 0%)
515515- - Note: Refresher merged into client.go
516516-11. 🔴 `pkg/auth/oauth/server.go` - OAuth endpoints (50.7%, continue improvements)
517517- - `ServeCallback()` at 16.3% needs major improvement
518518-12. 🔴 `pkg/appview/storage/crew.go` - Crew validation (11.1% → 80%+)
519519-13. 🔴 `pkg/auth/*` - Continue auth improvements (55.7% → 70%+)
520520- - hold_remote.go gaps, session.go
521521-14. 🎯 `pkg/atproto/*` - ATProto improvements (27.8%, continue adding tests)
522522- - directory_test.go, endpoints_test.go, utils_test.go added ✅
523523-524524-### Phase 3: Event Processing (Target: 55% overall)
525525-15. 🟡 `pkg/appview/jetstream/worker.go` - Event processing (0% → 70%+)
526526-16. 🟡 `pkg/appview/jetstream/backfill.go` - Backfill logic (0% → 70%+)
527527-17. 🟡 `pkg/hold/pds/*` - Fill in gaps in embedded PDS
528528-18. 🟡 `pkg/hold/oci/*` - OCI multipart upload improvements
529529-530530-### Phase 4: Web UI (Target: 60% overall)
531531-19. 🎯 `pkg/appview/handlers/*` - Web handlers (19.7%, greatly improved from 2.1%) **+17.6%** ✅
532532- - Continue adding handler tests to reach 50%+
533533-20. 🟢 `pkg/appview/routes/*` - Route registration (10.4% → 50%+)
534534-535535----
536536-537537-## Testing Best Practices for This Codebase
538538-539539-### For Middleware Tests
540540-- Mock HTTP handlers to test middleware wrapping
541541-- Use `httptest.ResponseRecorder` for response inspection
542542-- Test context injection and extraction
543543-- Mock ATProto client for PDS interactions
544544-545545-### For Storage Tests
546546-- Mock `distribution` interfaces (BlobStore, ManifestService)
547547-- Use in-memory implementations where possible
548548-- Test error propagation from underlying storage
549549-- Mock hold XRPC endpoints
550550-551551-### For Database Tests
552552-- Use in-memory SQLite (`:memory:`)
553553-- Run migrations in test setup
554554-- Clean up after each test
555555-- Test concurrent operations where relevant
556556-557557-### For Authorization Tests
558558-- Mock ATProto client for crew lookups
559559-- Test both legacy and new hold models
560560-- Test permission combinations
561561-- Mock service token acquisition
562562-563563-### For OAuth Tests
564564-- Mock HTTP servers for PDS endpoints
565565-- Test DPoP proof generation/validation
566566-- Test PAR request flow
567567-- Mock browser interaction
568568-569569-### For ATProto Tests
570570-- Mock HTTP responses for resolver tests
571571-- Test DID document parsing
572572-- Mock XRPC endpoints
573573-- Test authentication flows
574574-575575----
576576-577577-## Coverage Goals
578578-579579-**Current:** 39.0% (improved from 37.7%, +1.3%)
580580-**Previous:** 37.7% (improved from 33.5%, +4.2%)
581581-**Total improvement:** 39.0% vs 31.2% baseline = **+7.8%**
582582-583583-**Top Packages by Coverage:**
584584-- ✅ `pkg/hold`: 98.0% (excellent)
585585-- ✅ `pkg/s3`: 97.4% (excellent)
586586-- ✅ `pkg/appview/licenses`: 93.0% (excellent)
587587-- ✅ `pkg/appview`: 81.8% (excellent)
588588-- ✅ `pkg/logging`: 75.0% (good)
589589-590590-**Key File-Level Achievements:**
591591-- ✅ `pkg/appview/middleware/auth.go`: 98.8% avg (excellent)
592592-- ✅ `pkg/appview/middleware/registry.go`: 90.8% avg (excellent)
593593-- ✅ `pkg/appview/storage/manifest_store.go`: 85%+ (CRITICAL improvement from 0%)
594594-- ✅ `pkg/appview/storage/profile.go`: 98%+ (new file, excellent)
595595-- ✅ `pkg/atproto/client.go`: 74.8% (good)
596596-- ✅ `pkg/atproto/resolver.go`: 74.5% (good)
597597-598598-**Packages Needing Work:**
599599-- 🟡 `pkg/auth/token`: 68.8% (good)
600600-- 🟡 `pkg/appview/middleware`: 57.8% (package avg lowered by Repository())
601601-- 🟡 `pkg/auth`: 55.7% (stable)
602602-- 🟡 `pkg/hold/oci`: 51.9% (needs work)
603603-- 🟡 `pkg/appview/storage`: 51.4% (critical gaps remain)
604604-- 🟡 `pkg/auth/oauth`: 50.7% (improving, was 48.3%) 🎯 **+2.4%**
605605-- 🟡 `pkg/hold/pds`: 47.2% (needs work)
606606-- 🟡 `pkg/appview/db`: 41.2% (decreased from 44.8%, tracking more code paths) 🔴 **-3.6%**
607607-- 🟡 `pkg/atproto`: 27.8% (improving, was 26.1%) 🎯 **+1.7%**
608608-- 🟡 `pkg/appview/handlers`: 19.7% (greatly improved from 2.1%) 🎯 **+17.6%**
609609-610610-**Short-term Goal (Phase 1 completion):** 45%+
611611-- ✅ Cover all critical middleware (**COMPLETE**)
612612-- ✅ Cover ATProto client and resolver (**COMPLETE**)
613613-- ✅ Cover storage manifest operations (**COMPLETE** 🎉)
614614-- ⭐ Cover storage blob write operations (**HIGHEST PRIORITY** - Put/Create/Writer)
615615-- ⭐ Cover storage blob routing (**HIGH PRIORITY**)
616616-617617-**Medium-term Goal (Phase 2):** 50%+
618618-- Complete remaining storage layer (blob writes)
619619-- Improve database layer coverage (44.8% → 70%+)
620620-- Complete OAuth implementation (refresher.GetSession, server.ServeCallback)
621621-- Add storage crew validation
622622-623623-**Long-term Goal (Phase 3-4):** 55-60%
624624-- Event processing (jetstream)
625625-- Web UI handlers (currently 2.1%)
626626-- Comprehensive integration tests
627627-628628-**Realistic Target:** 55-60% (excluding some UI handlers and integration-heavy code)
629629-630630-**Note:** Package percentages may decrease as new files are added to coverage tracking, but this reflects improved test comprehensiveness, not regression. Focus on file-level coverage for critical paths.
631631-632632----
633633-634634-## Notes
635635-636636-- **Test files exist:** Most files in `pkg/` now have corresponding `*_test.go` files, but many functions remain at 0% coverage
637637-- **SQLite vs PostgreSQL:** Current tests use SQLite. For production multi-instance deployments, consider PostgreSQL tests
638638-- **Concurrency:** Many components (cache, token refresher, OAuth) have concurrency concerns that need explicit testing
639639-- **Integration Tests:** Consider adding integration tests that spin up a real PDS + hold service for end-to-end validation
640640-- **Mock Strategy:** Use interfaces (like `atproto.Client`) to enable easy mocking. Consider a mock package in `pkg/testing/`
641641-- **Critical path first:** Focus on middleware and storage layers before web UI, as these are essential for core registry operations
+7-1
pkg/appview/config.go
···7878 // ClientName is the OAuth client display name (from env: ATCR_CLIENT_NAME, default: "AT Container Registry")
7979 // Shown in OAuth authorization screens
8080 ClientName string `yaml:"client_name"`
8181+8282+ // ClientShortName is the short brand name for page titles (from env: ATCR_CLIENT_SHORT_NAME, default: "ATCR")
8383+ // Used in page titles and metadata where a shorter name is appropriate
8484+ ClientShortName string `yaml:"client_short_name"`
8185}
82868387// UIConfig defines web UI settings
···168172 cfg.Server.TestMode = os.Getenv("TEST_MODE") == "true"
169173 cfg.Server.OAuthKeyPath = getEnvOrDefault("ATCR_OAUTH_KEY_PATH", "/var/lib/atcr/oauth/client.key")
170174 cfg.Server.ClientName = getEnvOrDefault("ATCR_CLIENT_NAME", "AT Container Registry")
175175+ cfg.Server.ClientShortName = getEnvOrDefault("ATCR_CLIENT_SHORT_NAME", "ATCR")
171176172177 // Auto-detect base URL if not explicitly set
173178 cfg.Server.BaseURL = os.Getenv("ATCR_BASE_URL")
···201206 cfg.CredentialHelper.TangledRepo = "https://tangled.org/@evan.jarrett.net/at-container-registry"
202207203208 // Legal page configuration (for self-hosted instances)
204204- cfg.Legal.CompanyName = getEnvOrDefault("ATCR_LEGAL_COMPANY_NAME", "AT Container Registry")
209209+ // CompanyName defaults to ClientName if not explicitly set
210210+ cfg.Legal.CompanyName = getEnvOrDefault("ATCR_LEGAL_COMPANY_NAME", cfg.Server.ClientName)
205211 cfg.Legal.Jurisdiction = getEnvOrDefault("ATCR_LEGAL_JURISDICTION", "State of Texas, United States")
206212207213 // Build distribution configuration for compatibility with distribution library
+5-4
pkg/appview/handlers/auth.go
···1818 }
19192020 meta := NewPageMeta(
2121- "Login - ATCR",
2222- "Sign in to ATCR with your AT Protocol account to push and pull container images",
2323- ).WithCanonical("https://" + h.RegistryURL + "/login")
2121+ "Login - "+h.ClientShortName,
2222+ "Sign in to "+h.ClientShortName+" with your AT Protocol account to push and pull container images",
2323+ ).WithCanonical("https://" + h.RegistryURL + "/login").
2424+ WithSiteName(h.ClientShortName)
24252526 data := struct {
2627 PageData
···2829 ReturnTo string
2930 Error string
3031 }{
3131- PageData: NewPageData(r, h.RegistryURL),
3232+ PageData: NewPageData(r, &h.BaseUIHandler),
3233 Meta: meta,
3334 ReturnTo: returnTo,
3435 Error: r.URL.Query().Get("error"),
···11111212func (h *LearnMoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
1313 meta := NewPageMeta(
1414- "About ATCR - Decentralized Container Registry on AT Protocol",
1515- "Learn how ATCR brings Docker container registries to the decentralized web using AT Protocol. Own your data, use your identity.",
1616- ).WithCanonical("https://" + h.RegistryURL + "/learn-more")
1414+ "About "+h.ClientShortName+" - Decentralized Container Registry on AT Protocol",
1515+ "Learn how "+h.ClientShortName+" brings Docker container registries to the decentralized web using AT Protocol. Own your data, use your identity.",
1616+ ).WithCanonical("https://" + h.RegistryURL + "/learn-more").
1717+ WithSiteName(h.ClientShortName)
17181819 data := struct {
1920 PageData
2021 Meta *PageMeta
2122 }{
2222- PageData: NewPageData(r, h.RegistryURL),
2323+ PageData: NewPageData(r, &h.BaseUIHandler),
2324 Meta: meta,
2425 }
2526
+10-8
pkg/appview/handlers/legal.go
···19192020func (h *PrivacyPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2121 meta := NewPageMeta(
2222- "Privacy Policy - ATCR",
2323- "ATCR privacy policy - how we collect, use, and protect your data on the decentralized container registry",
2424- ).WithCanonical("https://" + h.RegistryURL + "/privacy")
2222+ "Privacy Policy - "+h.ClientShortName,
2323+ h.ClientShortName+" privacy policy - how we collect, use, and protect your data on the decentralized container registry",
2424+ ).WithCanonical("https://" + h.RegistryURL + "/privacy").
2525+ WithSiteName(h.ClientShortName)
25262627 data := LegalPageData{
2727- PageData: NewPageData(r, h.RegistryURL),
2828+ PageData: NewPageData(r, &h.BaseUIHandler),
2829 Meta: meta,
2930 CompanyName: h.CompanyName,
3031 Jurisdiction: h.Jurisdiction,
···43444445func (h *TermsOfServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4546 meta := NewPageMeta(
4646- "Terms of Service - ATCR",
4747- "ATCR terms of service - rules and guidelines for using the decentralized container registry",
4848- ).WithCanonical("https://" + h.RegistryURL + "/terms")
4747+ "Terms of Service - "+h.ClientShortName,
4848+ h.ClientShortName+" terms of service - rules and guidelines for using the decentralized container registry",
4949+ ).WithCanonical("https://" + h.RegistryURL + "/terms").
5050+ WithSiteName(h.ClientShortName)
49515052 data := LegalPageData{
5151- PageData: NewPageData(r, h.RegistryURL),
5353+ PageData: NewPageData(r, &h.BaseUIHandler),
5254 Meta: meta,
5355 CompanyName: h.CompanyName,
5456 Jurisdiction: h.Jurisdiction,
+7
pkg/appview/handlers/meta.go
···1010 OGType string // OpenGraph type, defaults to "website"
1111 OGImage string // OpenGraph image URL (optional)
1212 TwitterCard string // Twitter card type, defaults to "summary_large_image"
1313+ SiteName string // Site name for og:site_name (optional, defaults to "ATCR")
1314 JSONLD []any // JSON-LD structured data objects (optional)
1415}
1516···5253 m.JSONLD = data
5354 return m
5455}
5656+5757+// WithSiteName sets the site name for og:site_name.
5858+func (m *PageMeta) WithSiteName(name string) *PageMeta {
5959+ m.SiteName = name
6060+ return m
6161+}
···99 {{ template "nav" . }}
10101111 <main class="container mx-auto px-4 py-8 max-w-4xl">
1212- <h1 class="text-3xl font-bold mb-2">Install ATCR Credential Helper</h1>
1313- <p class="text-base-content/70 mb-8">The ATCR credential helper enables Docker to authenticate with ATCR registries using your ATProto identity.</p>
1212+ <h1 class="text-3xl font-bold mb-2">Install {{ .ClientShortName }} Credential Helper</h1>
1313+ <p class="text-base-content/70 mb-8">The {{ .ClientShortName }} credential helper enables Docker to authenticate with {{ .ClientShortName }} registries using your ATProto identity.</p>
14141515 <div class="space-y-12">
1616 <section>
+1-1
pkg/appview/templates/pages/learn-more.html
···1313 <section class="text-center mb-16">
1414 <h1 class="text-4xl md:text-5xl font-bold mb-4">Docker meets the decentralized web</h1>
1515 <p class="text-xl text-base-content/70 max-w-2xl mx-auto">
1616- ATCR is an OCI-compliant container registry built on the AT Protocol.
1616+ {{ .ClientName }} is an OCI-compliant container registry built on the AT Protocol.
1717 Push and pull Docker images using your Bluesky identity.
1818 </p>
1919 </section>
+1-1
pkg/appview/templates/pages/login.html
···10101111 <main class="min-h-[calc(100vh-4rem)] flex items-center justify-center px-4">
1212 <div class="w-full max-w-md">
1313- <h1 class="text-2xl font-semibold text-center mb-2">Sign in to ATCR</h1>
1313+ <h1 class="text-2xl font-semibold text-center mb-2">Sign in to {{ .ClientName }}</h1>
1414 <p class="text-center text-base-content/60 mb-6">Use your ATProto handle to sign in</p>
15151616 {{ if .Error }}
+4-4
pkg/appview/templates/pages/privacy.html
···5757 <li>Cached metadata from your PDS (indexes for search and display)</li>
5858 </ul>
59596060- <h3 class="text-lg font-medium mt-6">ATCR-Hosted Hold Services</h3>
6060+ <h3 class="text-lg font-medium mt-6">{{ .ClientShortName }}-Hosted Hold Services</h3>
6161 <p>Storage backends we operate (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .RegistryURL }}</code>). Each hold has an embedded PDS and stores:</p>
6262 <ul class="list-disc list-inside space-y-1 ml-4">
6363 <li>OCI blobs (container image layers) in object storage</li>
···109109 <li>Server logs containing your identifiers (deleted or anonymized, if retained)</li>
110110 </ul>
111111112112- <p class="mt-4"><strong>Immediately deleted from ATCR-hosted holds:</strong></p>
112112+ <p class="mt-4"><strong>Immediately deleted from {{ .CompanyName }}-hosted holds:</strong></p>
113113 <ul class="list-disc list-inside space-y-1 ml-4">
114114 <li>Layer records in the hold's embedded PDS that reference your DID</li>
115115 <li>Crew membership records</li>
116116 </ul>
117117118118- <p class="mt-4"><strong>Deleted within 30 days from ATCR-hosted holds:</strong></p>
118118+ <p class="mt-4"><strong>Deleted within 30 days from {{ .CompanyName }}-hosted holds:</strong></p>
119119 <ul class="list-disc list-inside space-y-1 ml-4">
120120 <li>OCI blobs in object storage that are no longer referenced by any user (via garbage collection)</li>
121121 </ul>
···279279280280 <p>{{ .CompanyName }} supports "Bring Your Own Storage" where users can deploy their own hold services to store container image blobs. This section explains how BYOS affects your privacy rights.</p>
281281282282- <h3 class="text-lg font-medium mt-4">ATCR-Hosted Holds</h3>
282282+ <h3 class="text-lg font-medium mt-4">{{ .CompanyName }}-Hosted Holds</h3>
283283 <p>Hold services on <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">*.{{ .RegistryURL }}</code> domains (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .RegistryURL }}</code>) are operated by us and fully covered by this privacy policy. We can fulfill all data access, export, and deletion requests for these services.</p>
284284285285 <h3 class="text-lg font-medium mt-6">User-Deployed Holds</h3>
+8-8
pkg/appview/templates/pages/settings.html
···211211212212 <div class="space-y-4">
213213 <div>
214214- <h3 class="font-semibold">Delete ATCR Data</h3>
215215- <p class="text-base-content/70 mt-1">Remove your data from ATCR. This action cannot be undone.</p>
214214+ <h3 class="font-semibold">Delete {{ .ClientShortName }} Data</h3>
215215+ <p class="text-base-content/70 mt-1">Remove your data from {{ .ClientShortName }}. This action cannot be undone.</p>
216216 </div>
217217218218 <div class="alert bg-base-200">
219219 {{ icon "info" "size-5 shrink-0" }}
220220- <span><strong>This does not delete your ATProto (Bluesky, Blacksky, Tangled) account.</strong><br>Only ATCR-specific data (authorized devices, hold memberships, settings) will be removed.</span>
220220+ <span><strong>This does not delete your ATProto (Bluesky, Blacksky, Tangled) account.</strong><br>Only {{ .ClientShortName }}-specific data (authorized devices, hold memberships, settings) will be removed.</span>
221221 </div>
222222223223 <div class="space-y-2">
···226226 <span class="text-sm">Also delete all <code class="cmd">io.atcr.*</code> records from my ATProto PDS</span>
227227 </label>
228228 <p class="text-xs text-base-content/60 ml-7">
229229- This removes ATCR records (manifests, tags, stars, profile) stored in your PDS.
229229+ This removes {{ .ClientShortName }} records (manifests, tags, stars, profile) stored in your PDS.
230230 Other records in your account are not impacted.
231231 </p>
232232 </div>
233233234234 <button type="button" id="delete-account-btn" class="btn btn-error btn-lg gap-2">
235235 {{ icon "trash-2" "size-5" }}
236236- Delete My ATCR Data
236236+ Delete My {{ .ClientShortName }} Data
237237 </button>
238238 </div>
239239 </section>
···321321 <div class="modal-box max-w-lg">
322322 <h2 class="text-xl font-bold flex items-center gap-2 text-error">
323323 <svg class="icon size-6" aria-hidden="true"><use href="/icons.svg#alert-triangle"></use></svg>
324324- Delete ATCR Data
324324+ Delete {{ .ClientShortName }} Data
325325 </h2>
326326327327 <div class="py-4 space-y-4">
···335335 </p>
336336337337 <ul class="list-disc list-inside text-sm space-y-1 text-base-content/70">
338338- <li>Your ATCR account and all settings</li>
338338+ <li>Your {{ .ClientShortName }} account and all settings</li>
339339 <li>All authorized devices</li>
340340 <li>Your data from all holds you're a member of</li>
341341 ${document.getElementById('delete-pds-records').checked ?
···352352 <button type="button" class="btn" id="cancel-delete">Cancel</button>
353353 <button type="button" class="btn btn-error gap-2" id="confirm-delete" disabled>
354354 <svg class="icon size-4" aria-hidden="true"><use href="/icons.svg#trash-2"></use></svg>
355355- Delete My ATCR Data
355355+ Delete My {{ .ClientShortName }} Data
356356 </button>
357357 </div>
358358 </div>
+1-1
pkg/appview/templates/pages/user.html
···3737 <!-- Content -->
3838 {{ if not .HasProfile }}
3939 <div class="text-center text-base-content/60 py-12">
4040- <p>This user hasn't set up their ATCR profile yet.</p>
4040+ <p>This user hasn't set up their {{ .ClientShortName }} profile yet.</p>
4141 </div>
4242 {{ else }}
4343 <div class="w-full">