The /s/:token route was registered outside the /api/* db middleware
scope, causing db to be undefined and a 500 error on share link access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
updateSetupSchema required name as mandatory, causing ZodError when
ShareModal sent visibility-only updates. Made name optional in update
schema and guarded against setting undefined name in service layer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-fixed formatting issues and removed unused imports introduced
by background execution agents across currency, i18n, and sharing code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create all 6 German namespace JSON files (common, collection, threads, setups, onboarding, settings)
- Register German locale in i18n configuration with supportedLngs
- Add key parity test ensuring en/de have identical key structures
- All 19 locale parity tests pass, all 15 formatter tests pass
Phase 34, Plan 05
- Add language picker (English/Deutsch) to settings page using pill-toggle pattern
- Import useLanguage hook and i18n instance in settings
- Language change persists via updateSetting and calls i18n.changeLanguage
- Add useEffect in RootLayout to sync i18n language with DB setting on load
- Language labels use native names (English, Deutsch) for identification
Phase 34, Plan 04
- Create useLanguage() hook following useCurrency/useWeightUnit pattern
- Update formatPrice() to use Intl.NumberFormat for locale-aware currency display
- Update formatWeight() to use Intl.NumberFormat for locale-aware number formatting
- Update formatDualPrice() to pass locale through
- Update useFormatters() to pass locale to all formatters
- Add formatter tests for en/de locales (15 tests passing)
Phase 34, Plan 03
- Add useGlobalItemPrices and useGlobalItemCommunityStats hooks
- Add MarketPricesSection component with user's market MSRP prominent
- Show community price stats per market with median and report count
- Collapsible "Other Markets" section (collapsed by default)
- Import useCurrency, useExchangeRates, formatPrice for market display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create community-price.service.ts with ownership validation, upsert, median aggregation
- Create community-prices route (GET stats public, POST requires auth + ownership)
- Register community-prices route with public GET access
- Add priceCurrency to both getSetupWithItems and getSetupWithItemsById
- Aggregation uses PERCENTILE_CONT(0.5) with 3-report minimum threshold
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create market-price.service.ts with getMarketPrices, upsertMarketPrice
- Create exchange-rates route (GET /api/exchange-rates, public)
- Create market-prices route (GET/POST /api/market-prices/global-items/:id/prices)
- Register new routes in server index with public GET access
- Add priceCurrency to item service getAllItems/getItemById/createItem
- Add foundPriceCents/Currency/Date to thread candidate select and create/update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect ?share=token query param on setup detail page, fetch via
/api/shared/:token, and display read-only view with "Shared setup"
banner. Hide all owner controls (add items, share, delete, classification)
in shared view. Show "Link not available" error for invalid tokens.
Plan: 32-04 (Setup Sharing System - Shared Setup Viewer)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create ShareModal component with three-tier visibility picker
(private/link/public), share link creation with configurable expiration,
clipboard copy, and link revocation. Wire into setup detail page
replacing the static visibility badge with an interactive share button.
Plan: 32-03 (Setup Sharing System - Share Modal UI)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create share.service.ts with token generation (128-bit base64url),
CRUD operations, validation, and visibility transition side effects.
Add share endpoints under /api/setups/:id/shares, shared access at
/api/shared/:token, and /s/:token short URL redirect.
Plan: 32-02 (Setup Sharing System - Share Link Backend)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace isPublic boolean with visibility enum (private/link/public) across
the full stack. Add shares table to schema for future share link support.
Update all services, routes, schemas, hooks, components, and tests.
Plan: 32-01 (Setup Sharing System - Schema Migration)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved the crop button from below the image into the ImageUpload
component as an absolute-positioned overlay next to the trash icon,
matching the visual pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The crop icon button was in the view-mode branch but conditioned on
isEditing, making it unreachable. Moved it below ImageUpload in the
edit-mode branch where it belongs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ImageUpload was discarding the dominantColor returned by the upload
API. Now it passes the color through onChange and the item detail
page saves it to the item record immediately after upload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OIDC session token retains the old email after a Logto email
change. Now the server returns the new email in the response and
the frontend optimistically updates the auth cache.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs the URL, resource, app ID prefix, and response body when the
token request fails — helps diagnose 400 errors from Logto.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UserMenu now fetches the user's profile and displays their avatar
image in the nav button instead of the default circle-user icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avatar images were rendered via /uploads/ which doesn't exist since
the S3 migration. Now the server enriches profile responses with
avatarImageUrl (presigned S3 URL) and the frontend uses it directly.
Also fixed the public profile page at /users/:id.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Selected hobby cards now use dark gray fill with inverted white
text/icon for clear visual distinction. Also fixes biome formatting
across all changed files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed "Adjust framing" text to a crop icon button visible only in
edit mode. Replaced the X icon on the image remove button with a
trash icon for clearer semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added onCropChange and dominantColor props to ImageUpload in the item
detail page, so the crop editor opens automatically after uploading
a new image.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced the one-shot initialized flag with a dirty flag that allows
the useEffect to re-sync local state from server data after a
successful save. Previously, once initialized was set to true, the
effect never ran again so avatar changes were lost on refetch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The flat list was missing dominantColor/crop props, and the grouped
view was also missing imageUrl entirely — causing images not to render
on collection cards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getAllItems and getItemById were not selecting dominantColor, cropZoom,
cropX, cropY from the database. GearImage was ignoring the dominantColor
prop. Now the fields flow end-to-end from DB to UI background fill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The updateItem function's TypeScript type was missing dominantColor,
cropZoom, cropX, and cropY fields, causing crop settings to silently
fail to save despite the Zod schema and DB schema supporting them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Behind a reverse proxy, c.req.url resolves to internal URL which
doesn't match the registered post_logout_redirect_uri in Logto.
Use GEARBOX_URL env var (already required for OAuth) as the
redirect target.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto needs client_id to validate the post_logout_redirect_uri and
auto-redirect back to the app. Without it, user gets stuck on
Logto's end-session success page.
Note: post_logout_redirect_uri must be registered in Logto Console
under the app's "Post sign-out redirect URIs".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After revoking the local session, redirect to Logto's /session/end
so the OIDC session is cleared too. Previously redirected to /login
which immediately re-authenticated via the still-valid Logto session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useLogout() returns { logout } but was assigned directly, causing
"r is not a function" when clicking sign out.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Zod schema rejected null for avatarUrl, but the client sends null
when the avatar is removed. Changed to z.string().nullable().optional().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Swap old 4-step modal wizard with new full-screen, hobby-personalized
onboarding experience. Delete OnboardingWizard.tsx.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements 5-step onboarding: Welcome, Hobby Picker, Item Browser,
Review, and Done. Includes hobby card selection, popular item grid
with check/uncheck, review list with remove, CSS step transitions,
and responsive grid layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>