Zoom+pan editor using react-easy-crop with zoom slider, save/cancel
buttons, and dominant color background. Returns crop coordinates
for persistence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace object-cover on item detail, global item detail, candidate
detail, global items index, and LinkToGlobalItem. Detail pages use
dominant color backgrounds. LinkToGlobalItem uses cover mode for
32px thumbnails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace object-cover with GearImage across ComparisonTable,
CatalogSearchOverlay (2 instances), and ImageUpload preview.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace object-cover with GearImage for fit-within rendering on
candidate cards and list items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace object-cover with GearImage component for fit-within rendering.
Add dominantColor and crop props to both card components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Renders images with object-contain by default (letterbox/pillarbox),
object-cover when cover prop is set, or CSS transform when crop
values are present. Parent container uses dominant color background.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add dominantColor, cropZoom, cropX, cropY to createItemSchema,
createCandidateSchema, and upsertGlobalItemSchema.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both POST /api/images and POST /api/images/from-url now return
dominantColor in their response body.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
extractDominantColor() resizes image to 1x1 pixel for weighted average
color. Integrated into fetchImageFromUrl to return dominantColor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add dominant_color, crop_zoom, crop_x, crop_y columns to items,
global_items, and thread_candidates tables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Profile link to UserMenu dropdown (above Settings), extends /me
endpoint to return user's createdAt for member-since display, and
updates AuthState interface with optional createdAt field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds /profile route with four sections: profile info (reuses ProfileSection),
account info (email + member since), security (password change/set), and
danger zone (account deletion with typed confirmation). Removes ProfileSection
from settings page per D-01.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates /api/account routes with password change (verifies current first),
email update, has-password check, and account deletion with public setup
anonymization. Adds Zod validation schemas and registers routes in index.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements LogtoManagementClient with token caching, password verification,
password update, email update, user deletion, and has-password check.
All methods proxy to Logto Management API via M2M credentials.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Two-row sticky toolbar: search input + view toggle (Row 1), tag/weight/price filter pills (Row 2)
- Tag filter popover with click-outside close via useRef/useEffect
- Weight and price range filter popovers with min/max sliders
- Active filter removable pills + Clear all button
- Grid view uses existing GlobalItemCard, list view uses Link-based GlobalItemListRow
- SkeletonGrid and SkeletonList loading states
- Empty state with context-aware message (query vs no catalog items)
- Search input pre-fills from ?q= URL param, debounces 300ms
- No framer-motion, no manual entry mode, no Add buttons
- Add validateSearch with z.object({ q }) to route definition
- Use Route.useSearch() to get q param instead of local state
- Remove duplicate search input UI, debounce state and useEffect
- Show "Showing results for X" label when q is present
- Update empty state text based on whether q param exists
- Replace fake button with real text input and search icon
- Navigate to /global-items?q=query on Enter or icon click
- Clear input after navigation
- Remove openCatalogSearch usage from TopNav (FAB/BottomTabBar flows unchanged)
- Replace arrow entity + "Dashboard" back link with ArrowLeft icon + "Discover"
- Consolidate title and search into a single flex row (wraps on mobile)
- Reduce outer padding from py-6 to py-4
- Remove subtitle paragraph and separate mb-6/mb-8 section margins
- Delete HeroSection function (Discover Gear heading, search bar, Go to Collection link)
- Remove unused imports: Link, Search (lucide-react), useAuth, useUIStore
- LandingPage now starts directly with PopularSetupsSection
- Search now exclusively in TopNav bar
- Replace TotalsBar import with TopNav and BottomTabBar imports
- Remove isDashboard and totalsBarProps variables
- Render TopNav instead of TotalsBar
- Add /setups to isPublicRoute for anonymous direct navigation
- Wrap FabMenu in hidden md:block for mobile hiding
- Add BottomTabBar after FAB block (md:hidden in component itself)
- Add pb-16 md:pb-0 to root div to prevent content occlusion by bottom tab bar
- Fixed bottom tab bar for mobile (md:hidden) with z-20 stacking
- 4 tabs: Home, Collection, Setups, Search with Lucide icons
- Collection and Setups fire openAuthPrompt for anonymous users
- Search tab calls openCatalogSearch('collection') to open overlay
- Active route highlighting via useMatchRoute
- Framer Motion entry animation (y slide + fade)
- iOS safe area padding with env(safe-area-inset-bottom)
[Rule 1 - Bug] Used 'house' icon instead of 'home': lucide-react has no 'Home' icon (only 'House')
- Sticky top nav bar replacing TotalsBar with full navigation
- Logo, Home/Collection/Setups links, search bar, and user avatar
- NavLinkOrButton helper: button for anon users on protected routes, Link for authenticated
- Active route highlighting via useMatchRoute
- Desktop search bar triggers openCatalogSearch('collection')
- Desktop nav links hidden on mobile (hidden md:flex)
- Uses LucideIcon wrapper, not direct lucide-react imports
[Rule 1 - Bug] Used 'house' icon fallback check: plan specified 'home' which does not exist in lucide-react; 'search' and 'layers' verified present
Remove unused functions and imports from route tests, fix array index key
warnings in skeleton components, apply biome formatting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace DashboardPage with LandingPage using discovery hooks
- Add HeroSection with Discover Gear heading and catalog search trigger
- Add PopularSetupsSection using useDiscoverySetups with PublicSetupCard
- Add RecentItemsSection using useDiscoveryItems with GlobalItemCard
- Add TrendingCategoriesSection using useDiscoveryCategories with pills
- Conditional Go to Collection CTA for authenticated users
- Loading skeletons with animate-pulse for all three sections
- Empty state handling: sections return null when no data
- SectionSkeleton helper for consistent loading states
- All clickable elements have cursor-pointer
- Create src/server/routes/discovery.ts with GET /setups, /items, /categories handlers
- Register discoveryRoutes in src/server/index.ts with browseTier rate limiting
- Add auth skip for /api/discovery/* GET requests in auth middleware
- Create tests/routes/discovery.test.ts with 10 tests covering all endpoints and pagination
- GlobalItem interface extended with sourceUrl, imageCredit, imageSourceUrl fields
- Attribution block below image: Photo credit and source link when present
- Product page link (sourceUrl) at bottom of detail page
- Image div mb-6 moved to attribution paragraph for consistent spacing
- New catalog.ts with catalogToolDefinitions and registerCatalogTools
- upsert_catalog_item: single item upsert with full attribution fields (SEED-03)
- bulk_upsert_catalog: batch upsert up to 100 items with created/updated counts
- Registered in createMcpServer after image tools
- 6 new MCP catalog tool tests passing
- POST /api/global-items upserts single item via upsertGlobalItem service
- POST /api/global-items/bulk upserts up to 100 items via bulkUpsertGlobalItems service
- Zod validation via @hono/zod-validator with upsertGlobalItemSchema and bulkUpsertGlobalItemsSchema
- Add upsertGlobalItemSchema and bulkUpsertGlobalItemsSchema to schemas.ts
- Add UpsertGlobalItemInput and BulkUpsertGlobalItemsInput types to types.ts
- Implement upsertGlobalItem with onConflictDoUpdate and tag sync
- Implement bulkUpsertGlobalItems processing array in single transaction
- Fix migration 0003 to only add new columns + unique constraint
- All 21 tests pass including 8 new upsert operation tests
Public setup view was missing image URL enrichment, causing item images
to be absent for anonymous visitors. Matches the private endpoint pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove authLoading spinner gate — app renders immediately for all visitors
- Expand isPublicRoute to include /, /global-items/*, /setups/*, /users/, /login
- Replace hard window.location.href redirect with soft navigate() after auth resolves
- Remove onboarding loading spinner — pass isAuthenticated as enabled to guard query
- Add AuthPromptModal to root JSX for global availability
- Guard Add to Collection and Add to Thread buttons with isAuthenticated check
- Rework setup detail page to use usePublicSetup for anonymous visitors
- Wrap all write action UI (Add Items, Delete, Public toggle, remove/classify) in isAuthenticated guards
- Import createRateLimit in server index
- Create browseTier (120 req/min) for list/search endpoints
- Create detailTier (60 req/min) for individual resource endpoints
- Apply browseTier to /api/global-items and /api/tags GET routes
- Apply detailTier to /api/global-items/:id, /api/setups/:id/public, /api/users/:id/profile GET routes
- Rate limits placed before auth middleware per D-07, D-08
- Extend uiStore with showAuthPrompt/openAuthPrompt/closeAuthPrompt state
- Create AuthPromptModal component with sign in/sign up CTAs pointing to /login
- Add usePublicSetup hook to useSetups for anonymous setup viewing via public API
- Rework useOnboardingComplete to accept enabled param (guards auth-gated call)
- Add createRateLimit(maxAttempts, windowMs) factory function
- Rewrite rateLimit export to delegate to factory (backward compatible)
- Keep shared store, getClientIp, cleanup, and _resetForTesting unchanged
- Add createRateLimit factory test suite with 5 test cases
- All existing rateLimit middleware tests still pass
activeThreads[0].id in useEffect dependency array threw when the array
was empty. Use optional chaining to safely handle the empty case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /me endpoint was returning auth.sub (Logto's opaque string) as the
user ID, but the frontend and other API endpoints expect numeric DB IDs.
This caused "can't access property 'id', w[0] is undefined" after login.
Also documents Logto OIDC setup requirements (scopes, env vars) in
CLAUDE.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @hono/oidc-auth middleware catches all errors and rethrows as
"Invalid session", hiding the real cause. This adds a startup probe
to OIDC discovery endpoint so the actual error appears in logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OAuth: add userId to oauth_codes schema and migration, derive userId
from stored auth code/token record instead of passing separately
- Auth middleware tests: destructure {db, userId} from createTestDb,
pass userId to createApiKey, fix error message assertion
- MCP tests: add missing await on getCollectionSummary and
createSecondTestUser calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>