Plans 03 and 04 both modify setups/$setupId.tsx. Per wave assignment
rules, file overlap requires sequential execution. Plan 04 now depends
on Plan 03 and runs in Wave 4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phases 28-31 archived to milestones/v2.2-phases/
Requirements and roadmap snapshots archived to milestones/
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>