diff --git a/.planning/ui-reviews/.gitignore b/.planning/ui-reviews/.gitignore new file mode 100644 index 0000000..5393559 --- /dev/null +++ b/.planning/ui-reviews/.gitignore @@ -0,0 +1,8 @@ +# Screenshot files — never commit binary assets +*.png +*.webp +*.jpg +*.jpeg +*.gif +*.bmp +*.tiff diff --git a/.planning/ui-reviews/UI-REVIEW.md b/.planning/ui-reviews/UI-REVIEW.md new file mode 100644 index 0000000..2127305 --- /dev/null +++ b/.planning/ui-reviews/UI-REVIEW.md @@ -0,0 +1,66 @@ +# Phase Standalone — UI Review + +**Audited:** 2024 +**Baseline:** Abstract 6-Pillar Standards +**Screenshots:** Not captured (code-only audit) + +--- + +## Pillar Scores + +| Pillar | Score | Key Finding | +|--------|-------|-------------| +| 1. Copywriting | 3/4 | Error states use generic 'Something went wrong'. | +| 2. Visuals | 3/4 | Standard component structures, but icon accessibility could be improved. | +| 3. Color | 2/4 | Heavy use of hardcoded colors in `DevToolbar.tsx`. | +| 4. Typography | 3/4 | Uses custom CSS styles rather than utility classes; mostly consistent. | +| 5. Spacing | 3/4 | Uses custom CSS styles; no major arbitrary inline spacing found. | +| 6. Experience Design | 3/4 | Good coverage of loading/error states in MapView and PasswordGate. | + +**Overall: 17/24** + +--- + +## Top 3 Priority Fixes + +1. **Refactor Colors in DevToolbar** — Hardcoded hex values break theme consistency and dark mode — Extract colors to CSS variables in the theme layer. +2. **Improve Error Copy in PasswordGate** — Generic "Something went wrong" frustrates users — Replace with specific, actionable messaging like "Incorrect password, please try again." +3. **Enhance MapView Loading State** — Simple boolean loading state causes layout shifts — Implement a skeleton loader or a dedicated map placeholder to improve perceived performance. + +--- + +## Detailed Findings + +### Pillar 1: Copywriting (3/4) +- **PasswordGate.tsx**: Uses standard CTA ('Enter', 'Verifying...') but error state falls back to generic "Something went wrong" at line 40. + +### Pillar 2: Visuals (3/4) +- Components use semantic HTML for the most part. SVG icons in `ThemeToggle`, `Lightbox`, and `Footer` lack robust `aria-label` alternatives for screen readers in some instances. + +### Pillar 3: Color (2/4) +- **DevToolbar.tsx**: Contains over 20 distinct hardcoded hex values (clues: `#e60012`, `#e4e4e7`, `#166534`, `#ff6b35`). This violates the single-source-of-truth theme approach. +- **Lightbox / SetupScreen**: Minimal hardcoded values used for fallback (`#000`, `#ffffff`). + +### Pillar 4: Typography (3/4) +- Exclusively uses CSS module classes rather than utility variables. +- Some inline font sizes exist (e.g., `fontSize: 10` in DevToolbar). + +### Pillar 5: Spacing (3/4) +- Relies cleanly on CSS modules. +- Only a few inline spacing overrides (e.g., `marginTop: 6` in DevToolbar). + +### Pillar 6: Experience Design (3/4) +- **PasswordGate.tsx**: Correctly disables the submit button and indicates processing state (`loading ? 'Verifying...' : 'Enter'`). +- **MapView.tsx**: Handles API mapping errors gracefully (`error &&
{error}
`). + +--- + +## Files Audited +- `app/api/auth/route.ts` +- `app/api/health/route.ts` +- `components/PasswordGate.tsx` +- `components/MapView.tsx` +- `components/Lightbox.tsx` +- `components/DevToolbar.tsx` +- `components/ThemeToggle.tsx` +- `components/Footer.tsx` diff --git a/app/api/image/[id]/route.ts b/app/api/image/[id]/route.ts index 79115a0..5df32ba 100644 --- a/app/api/image/[id]/route.ts +++ b/app/api/image/[id]/route.ts @@ -61,6 +61,20 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } } + // ── Browser Cache Optimization ───────────────────── + // Use the opaque token to generate a safe ETag without leaking Immich UUIDs + const etag = `W/"${token}-${size}"`; + if (request.headers.get('if-none-match') === etag) { + return new NextResponse(null, { + status: 304, + headers: { + 'ETag': etag, + 'Cache-Control': 'public, max-age=31536000, immutable', + 'X-RateLimit-Remaining': String(remaining), + }, + }); + } + const result = await immich.streamAsset(assetId, size); if (!result) { console.error(`[Image API] ❌ Asset not found in Immich: ${assetId} (Size: ${size})`); @@ -73,6 +87,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ : result.contentType, // Images are immutable once uploaded to Immich — cache aggressively 'Cache-Control': 'public, max-age=31536000, immutable', + 'ETag': etag, 'X-RateLimit-Remaining': String(remaining), }; diff --git a/components/DevToolbar.tsx b/components/DevToolbar.tsx index 66405fb..75d5c88 100644 --- a/components/DevToolbar.tsx +++ b/components/DevToolbar.tsx @@ -35,12 +35,12 @@ const PRESET_FONTS: Record{error}
Loading map…
+