Files
Jean-Luc Makiola 4ccbb2b070
Some checks failed
CI / ci (push) Failing after 1m44s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
fix: wire catalog add buttons, fix Trans bold rendering, lint cleanup
- CatalogSearchOverlay: replace handleAddStub with real openAddToCollection/openAddToThread routing based on catalogSearchMode
- ConfirmDialog + __root.tsx: swap t() for Trans component on deleteItemMessage, deleteCandidateMessage, pickWinnerMessage — fixes <bold> rendering as literal text
- Biome format pass: fix 23 lint/format errors across scripts, services, tests
- Planning: mark all UAT and verification gaps resolved for phases 07, 11, 16, 20, 21, 22, 24, 32, 34; close debug sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:36:16 +02:00

14 KiB

phase, verified, status, score, re_verification, gaps, human_verification
phase verified status score re_verification gaps human_verification
24-public-access-infrastructure 2026-04-10T12:00:00Z complete 5/6 must-haves verified false
truth status reason artifacts missing
Anonymous visitor can view a public setup with its items and totals partial Setup items display correctly but item images are missing for anonymous viewers. getPublicSetupWithItems does not call withImageUrls, so no presigned S3 URLs are generated. The $setupId.tsx component passes item.imageUrl (undefined) to ItemCard — confirmed TS2339 type error at line 284.
path issue
src/server/services/profile.service.ts getPublicSetupWithItems (line 87) does not call withImageUrls on the returned item list, unlike the private getSetupWithItems in setup.service.ts
path issue
src/client/routes/setups/$setupId.tsx Line 284: item.imageUrl is passed to ItemCard but SetupItemWithCategory only defines imageFilename. TypeScript error TS2339 confirms property does not exist on the type. Images silently not displayed for anonymous users.
Call withImageUrls on items in getPublicSetupWithItems, or add imageUrl to the service return type by enriching from storage service
Remove item.imageUrl reference from $setupId.tsx ItemCard props (or add imageUrl to SetupItemWithCategory after enrichment)
test expected why_human
Anonymous visitor can view a public setup page Setup renders with item list, weight totals, and cost totals. No Add Items / Delete / Public toggle buttons visible. Back arrow link works. Visual confirmation of rendered output and write-action absence requires browser
test expected why_human
Auth prompt modal behavior on catalog detail page Clicking 'Add to Collection' or 'Add to Thread' shows the modal. Backdrop click closes it. Escape key closes it. Both buttons route to /login. Modal interaction and keyboard events require browser verification
test expected why_human
No auth spinner or redirect on first anonymous visit App renders immediately at / and /global-items without redirect to /login or loading spinner Render timing and redirect behavior requires browser verification in an incognito session

Phase 24: Public Access Infrastructure Verification Report

Phase Goal: Anyone can browse the catalog, public setups, and user profiles without logging in Verified: 2026-04-10T12:00:00Z Status: gaps_found Re-verification: No — initial verification

Goal Achievement

Observable Truths

# Truth Status Evidence
1 Public GET endpoints return 429 after exceeding the configured rate limit VERIFIED createRateLimit factory confirmed in rateLimit.ts:23; 11 tests pass
2 Different endpoint tiers have different rate limit thresholds VERIFIED browseTier(120, 60_000) and detailTier(60, 60_000) confirmed in index.ts:122-123
3 Existing OAuth rate limiting (5 req/15 min) continues to work unchanged VERIFIED rateLimit = createRateLimit(5, 15 * 60 * 1000) at rateLimit.ts:44; backward-compat tests pass
4 Anonymous visitor sees app content immediately on any public route — no spinner, no redirect VERIFIED authLoading spinner block removed from __root.tsx; soft navigate() guard fires only after auth resolves and !authLoading
5 Anonymous visitor can browse the global item catalog and open catalog detail pages VERIFIED isPublicRoute includes pathname.startsWith("/global-items"); auth middleware skips GET /api/global-items; no auth required
6 Anonymous visitor can view a public setup with its items and totals PARTIAL Setup items and totals render correctly. Item images absent for anonymous viewers — getPublicSetupWithItems does not call withImageUrls; item.imageUrl is undefined (TS2339 at $setupId.tsx:284)
7 Anonymous visitor can view a user profile page VERIFIED isPublicRoute includes /users/; auth skips GET /api/users/:id/profile; getPublicProfile queries users + setups from DB
8 Anonymous visitor clicking 'Add to Collection' or 'Add to Thread' sees a sign-in/sign-up prompt VERIFIED openAuthPrompt() called before openAddToCollection/openAddToThread in $globalItemId.tsx:141-158; AuthPromptModal rendered globally in __root.tsx
9 Authenticated user experience is unchanged — all write actions work as before VERIFIED isAuthenticated guards all new branches; mutation hooks retained; private useSetup path unchanged

Score: 8/9 truths verified (1 partial)

Required Artifacts

Artifact Expected Status Details
src/server/middleware/rateLimit.ts createRateLimit factory function VERIFIED exports createRateLimit, rateLimit, _resetForTesting; 49 lines, substantive
src/server/index.ts Rate limit middleware applied to public GET endpoints VERIFIED browseTier and detailTier instantiated at lines 122-123; applied to 5 endpoint groups before auth middleware at line 151
tests/middleware/rateLimit.test.ts Tests for configurable rate limit tiers VERIFIED 181 lines; two describe blocks; createRateLimit factory with 5 tests; rateLimit backward compat with 6 tests; all 11 pass
src/client/routes/__root.tsx Render-first root layout with expanded isPublicRoute VERIFIED No authLoading spinner; isPublicRoute includes /global-items and /setups/; AuthPromptModal rendered
src/client/stores/uiStore.ts showAuthPrompt state for auth modal VERIFIED showAuthPrompt, openAuthPrompt, closeAuthPrompt in interface and implementation
src/client/components/AuthPromptModal.tsx Modal prompting anonymous users to sign in or sign up VERIFIED Contains "sign in or sign up"; fixed overlay z-50; backdrop dismiss; two /login links
src/client/hooks/useSetups.ts usePublicSetup hook for anonymous setup viewing VERIFIED usePublicSetup exported at line 67; calls /api/setups/${setupId}/public; enabled guard; 404-aware retry
src/client/routes/global-items/$globalItemId.tsx Auth-guarded write action buttons on catalog detail VERIFIED openAuthPrompt imported and called in both button handlers with !isAuthenticated check
src/client/routes/setups/$setupId.tsx Conditional public vs private setup rendering PARTIAL usePublicSetup imported and used; conditional data source correct; but item.imageUrl does not exist on SetupItemWithCategory type
From To Via Status Details
src/server/index.ts src/server/middleware/rateLimit.ts import createRateLimit WIRED Line 13: import { createRateLimit } from "./middleware/rateLimit.ts"; pattern createRateLimit(120, confirmed at line 122
src/client/routes/__root.tsx src/client/components/AuthPromptModal.tsx rendered in root layout WIRED Line 15: import; line 184: <AuthPromptModal /> in JSX
src/client/routes/global-items/$globalItemId.tsx src/client/stores/uiStore.ts openAuthPrompt action WIRED Line 19: const openAuthPrompt = useUIStore((s) => s.openAuthPrompt); called at lines 142 and 154
src/client/routes/setups/$setupId.tsx src/client/hooks/useSetups.ts usePublicSetup hook WIRED Line 11: usePublicSetup imported; lines 33-36: conditional fetch logic using hook

Data-Flow Trace (Level 4)

Artifact Data Variable Source Produces Real Data Status
$setupId.tsx setup usePublicSetupGET /api/setups/:id/publicgetPublicSetupWithItems DB queries: setups table (line 88) + setupItems JOIN items JOIN categories (line 95-132) FLOWING (items/totals); imageUrl STATIC (undefined — withImageUrls not called)
$globalItemId.tsx item useGlobalItemGET /api/global-items/:id DB query in globalItems service FLOWING
$userId.tsx profile usePublicProfileGET /api/users/:id/profilegetPublicProfile DB queries: users table (line 37) + setups (line 49) with SQL aggregates FLOWING

Behavioral Spot-Checks

Behavior Command Result Status
Rate limit tests pass bun test tests/middleware/rateLimit.test.ts 11 pass, 0 fail PASS
Lint clean in src/ bun run lint (src/ only) 0 errors in src/ (4 pre-existing .obsidian/ format errors) PASS
createRateLimit factory exported grep pattern in rateLimit.ts export function createRateLimit(maxAttempts: number, windowMs: number) at line 23 PASS
browseTier applied before auth Line order check in index.ts Rate limits lines 122-148, auth middleware line 151 PASS
Public setup endpoint exists setups.ts route check app.get("/:id/public" at line 45; delegates to getPublicSetupWithItems PASS
imageUrl on public setup items TS error check TS2339 at $setupId.tsx:284 — item.imageUrl does not exist on SetupItemWithCategory FAIL

Requirements Coverage

Requirement Source Plan Description Status Evidence
PUBL-01 24-02 Browse global item catalog without logging in SATISFIED isPublicRoute includes /global-items; auth middleware skips GET /api/global-items; catalog route accessible
PUBL-02 24-02 View public setups without logging in PARTIAL Setup items and totals accessible via usePublicSetup; images missing for anon users (withImageUrls not called in public endpoint)
PUBL-03 24-02 View user profiles without logging in SATISFIED isPublicRoute includes /users/; auth skips GET /api/users/:id/profile; getPublicProfile queries DB
PUBL-04 24-02 No auth spinner or redirect on first visit SATISFIED authLoading spinner block removed; isPublicRoute expanded; soft navigate() fires only after authLoading resolves
PUBL-05 24-02 Login required only for create/edit/delete SATISFIED Write actions in $globalItemId.tsx and $setupId.tsx guarded by isAuthenticated; unauthenticated users see AuthPromptModal
INFR-01 24-01 Public API endpoints rate-limited SATISFIED createRateLimit factory; browseTier (120/min) and detailTier (60/min) applied to all 5 public GET endpoint groups

All 6 requirements claimed by phase 24 plans are accounted for. No orphaned requirements found in REQUIREMENTS.md traceability table.

Anti-Patterns Found

File Line Pattern Severity Impact
src/client/routes/setups/$setupId.tsx 284 imageUrl={item.imageUrl} — property does not exist on SetupItemWithCategory Warning Item images silently absent for anonymous viewers of public setups. TypeScript error TS2339 confirms the type gap. No runtime crash but visual content gap.
src/server/services/profile.service.ts 87-135 getPublicSetupWithItems returns items without presigned image URLs Warning Companion to above — public endpoint does not enrich items with S3 presigned URLs. Private endpoint calls withImageUrls; public endpoint does not.

No TODO/FIXME/placeholder comments found in phase-modified files. No empty implementations. No console.log-only handlers.

Human Verification Required

1. Public Setup Page Renders Read-Only

Test: Open an incognito browser window, navigate to a public setup URL (e.g., /setups/1 if a public setup exists). Verify setup name, item list with weights/costs, and total weight/cost render. Confirm no "Add Items", "Delete Setup", or "Public/Private" toggle buttons are visible. Expected: Full read-only view of the setup. No write-action controls. Why human: Visual confirmation of rendered content and conditional UI requires browser.

2. Auth Prompt Modal Interaction

Test: Open an incognito window, navigate to a catalog item detail page (/global-items/:id), click "Add to Collection". Verify the AuthPromptModal appears with "Join GearBox" heading and "sign in or sign up" text. Test backdrop click, Escape key, and "Sign in" / "Create account" button routes. Expected: Modal appears on first click, dismisses on backdrop/Escape, both buttons navigate to /login. Why human: Modal interaction, keyboard events, and navigation behavior require browser verification.

3. Render-First on Anonymous Visit

Test: Open an incognito window, navigate to /. Verify the app renders immediately without a spinner. Navigate to /global-items. Confirm catalog loads without redirect to /login. Expected: Instant render, no spinner, no redirect. Why human: Render timing and absence of auth redirect requires browser observation.

Gaps Summary

One functional gap was found that prevents full goal achievement for PUBL-02:

Image display in public setup views — When an anonymous user views a public setup at /setups/:id, item images will not display. The root cause is a missing withImageUrls call in getPublicSetupWithItems. The private getSetupWithItems in setup.service.ts calls withImageUrls to generate presigned S3 URLs and attaches them as imageUrl on each item. The public equivalent in profile.service.ts does not. The client code ($setupId.tsx:284) passes item.imageUrl to ItemCard, but SetupItemWithCategory has no such field — TypeScript confirms this with TS2339. The result is silent: no crash, items and totals render, but images are absent.

The fix requires either: (a) calling withImageUrls in getPublicSetupWithItems and returning the enriched items, or (b) removing the imageUrl={item.imageUrl} prop from the public render path.

This gap does not block the core browsing experience (items, names, weights, totals all work) but falls short of the full read-only parity the phase intended.


Verified: 2026-04-10T12:00:00Z Verifier: Claude (gsd-verifier)