- SUMMARY.md created for 24-02 (auth prompt modal, render-first root, public setup viewing) - STATE.md updated: plan advanced, progress 100%, decisions recorded - ROADMAP.md updated: phase 24 complete (2/2 plans with SUMMARYs) - REQUIREMENTS.md: PUBL-01 through PUBL-05 marked complete
92 lines
4.6 KiB
Markdown
92 lines
4.6 KiB
Markdown
---
|
|
phase: 24-public-access-infrastructure
|
|
plan: 02
|
|
subsystem: client/auth
|
|
tags: [public-access, auth-prompt, anonymous-browsing, setup-sharing]
|
|
dependency_graph:
|
|
requires: []
|
|
provides: [public-route-access, auth-prompt-modal, anonymous-setup-viewing]
|
|
affects: [__root.tsx, uiStore, useSetups, useSettings, global-items-detail, setup-detail]
|
|
tech_stack:
|
|
added: []
|
|
patterns: [zustand-modal-state, conditional-hook-enabled, render-first-auth]
|
|
key_files:
|
|
created:
|
|
- src/client/components/AuthPromptModal.tsx
|
|
modified:
|
|
- src/client/stores/uiStore.ts
|
|
- src/client/hooks/useSetups.ts
|
|
- src/client/hooks/useSettings.ts
|
|
- src/client/routes/__root.tsx
|
|
- src/client/routes/global-items/$globalItemId.tsx
|
|
- src/client/routes/setups/$setupId.tsx
|
|
decisions:
|
|
- Both auth prompt CTA buttons point to /login — Logto handles sign-in and sign-up at the same OIDC endpoint
|
|
- usePublicSetup hits /api/setups/:id/public — separate endpoint for anonymous access without auth middleware
|
|
- useOnboardingComplete(enabled) guards the settings query — prevents 401 spam for anonymous users
|
|
- Soft navigate() replaces hard window.location.href for private route redirect
|
|
metrics:
|
|
duration_seconds: 258
|
|
completed_date: "2026-04-10"
|
|
tasks_completed: 3
|
|
files_changed: 6
|
|
---
|
|
|
|
# Phase 24 Plan 02: Public Access Infrastructure — Client Layer Summary
|
|
|
|
Public-first browsing implemented: anonymous visitors see content immediately, write actions show a friendly sign-in/sign-up prompt, and the setup detail page renders read-only for unauthenticated users via a dedicated public API endpoint.
|
|
|
|
## Tasks Completed
|
|
|
|
| # | Task | Commit | Files |
|
|
|---|------|--------|-------|
|
|
| 1 | Add auth prompt state, modal, usePublicSetup hook, guard onboarding | cd85715 | uiStore.ts, AuthPromptModal.tsx, useSetups.ts, useSettings.ts |
|
|
| 2 | Rework __root.tsx, guard write actions on catalog and setup pages | 7b0efae | __root.tsx, $globalItemId.tsx, $setupId.tsx |
|
|
| 3 | Verify public access flows (auto-approved in auto mode) | — | — |
|
|
|
|
## What Was Built
|
|
|
|
### uiStore.ts
|
|
Extended with `showAuthPrompt`/`openAuthPrompt`/`closeAuthPrompt` state following the existing Zustand boolean modal pattern.
|
|
|
|
### AuthPromptModal.tsx
|
|
New component rendered globally in `__root.tsx`. Fixed overlay with centered card, backdrop click-to-close, Escape key dismiss. Two buttons ("Sign in" and "Create account") both pointing to `/login` — Logto handles both flows at the same OIDC endpoint.
|
|
|
|
### usePublicSetup hook
|
|
Added to `useSetups.ts`. Calls `GET /api/setups/:id/public` with `enabled: setupId != null` guard and 404-aware retry logic. Returns `SetupWithItems` shape identical to the private endpoint.
|
|
|
|
### useOnboardingComplete(enabled)
|
|
Reworked from `useSetting("onboardingComplete")` delegation to a direct `useQuery` call that accepts an `enabled` parameter. Prevents auth-gated settings query from firing for anonymous users.
|
|
|
|
### __root.tsx
|
|
- Removed `authLoading` spinner gate — app renders immediately for all visitors
|
|
- Expanded `isPublicRoute` to include `/`, `/global-items/*`, `/setups/*`, `/users/*`, `/login`
|
|
- Replaced `window.location.href = "/login"` with `navigate({ to: "/login" })` (soft redirect, only fires after auth resolves and `!authLoading`)
|
|
- Removed `onboardingLoading` spinner gate
|
|
- Passes `isAuthenticated` to `useOnboardingComplete()` as the `enabled` param
|
|
- Added `<AuthPromptModal />` to JSX for global availability
|
|
|
|
### global-items/$globalItemId.tsx
|
|
"Add to Collection" and "Add to Thread" buttons now check `isAuthenticated` before executing. Unauthenticated users see the `AuthPromptModal` instead.
|
|
|
|
### setups/$setupId.tsx
|
|
- Conditionally uses `useSetup` (authenticated) or `usePublicSetup` (anonymous)
|
|
- All write action UI elements wrapped in `{isAuthenticated && ...}` guards: Add Items button, Public toggle, Delete Setup button and confirmation dialog, empty-state Add Items button
|
|
- ItemCard `onRemove` and `onClassificationCycle` props are `undefined` for anonymous users
|
|
- ItemPicker rendered only for authenticated users
|
|
|
|
## Deviations from Plan
|
|
|
|
None — plan executed exactly as written.
|
|
|
|
## Verification
|
|
|
|
- `bun run lint`: 0 errors in `src/` (4 pre-existing `.obsidian/` format errors)
|
|
- `bun test`: 247 pass, 15 fail — identical to pre-change baseline (failures are pre-existing `withImageUrl` module issue in storage service, unrelated to this plan)
|
|
|
|
## Known Stubs
|
|
|
|
None. The public setup endpoint (`/api/setups/:id/public`) must exist server-side for `usePublicSetup` to work — this is the responsibility of Plan 24-01 (server-side public access routes). If that plan has not yet run, anonymous users will see an error state instead of the setup content.
|
|
|
|
## Self-Check: PASSED
|