commits
Schema Changes:
- Add optional fragranceName and houseName fields to review records
- Reviews now cache fragrance metadata for durability when fragrances are deleted/renamed
- Regenerated API types from updated lexicon schema
Fragrance Deletion:
- Enable deletion even when reviews exist (with warnings)
- Show review count and impact in deletion UI
- Reviews display deleted fragrances as "Name (deleted)"
- Created EditFragrancePage component with edit/delete functionality
- Added route for /profile/:handle/fragrance/:rkey/edit
Review Editing After 24 Hours:
- Allow editing text notes at any time
- Allow changing fragrance assignment at any time
- Lock stage ratings after 24 hours
- Update validation logic to distinguish editable vs locked fields
- Show edit button for all reviews with appropriate functionality
Bug Fixes:
- Fix fragrance deletion using typed method instead of raw XRPC call
- Fix review deletion using typed method instead of raw XRPC call
- Fix edit button visibility for reviews >= 24 hours old
- Remove "or is private" from fragrance not found error (records are always public)
- Update cached fragrance metadata when editing old reviews
UI/UX Improvements:
- Show cached fragrance names in reviews when fragrance is deleted
- Updated edit UI for post-24h reviews (text and fragrance only)
- Better confirmation dialogs explaining what will change
- Clearer error messages and permissions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The production build was failing because package-lock.json was out of
sync with package.json, missing react@18.3.1 as a peer dependency.
Running npm install regenerated the lock file with all peer
dependencies properly resolved.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Install web-haptics library for mobile vibration feedback
- Create reusable Button component with context-based haptics
- 6 semantic contexts: navigation, cancel, destructive, creation, save, share
- Maps contexts to haptic patterns (success, nudge, error, custom 100ms)
- Create HapticLink wrapper for navigation links with 100ms haptic
- Add haptics to all interactive elements:
- Navigation links in Header, TabBar, and all pages
- Action buttons across all forms and pages
- Review cards and house cards
- Haptics are optional via context prop, maintaining backward compatibility
- Gracefully degrades on desktop (no errors, no vibration)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements service-agnostic URLs for share buttons, profile links, and
footer links. Detects which service users are logged in with (Bluesky,
Blacksky, etc.) and adapts all external links accordingly.
Key features:
- Service configuration system for easy addition of new services
- OAuth issuer detection for logged-in users
- DID-based service detection for profile links
- Share buttons use logged-in user's service
- Profile handles link to user's service (right-aligned with external icon)
- Blacksky uses DID-based profile URLs, Bluesky uses handle-based
- Defaults to Blacksky for logged-out/unknown users
- Comprehensive documentation for adding new services
Technical implementation:
- ServiceProvider context wraps entire app
- useShareButton hook for compose intents
- Service detection via OAuth issuer and PDS URL patterns
- Supports handle-based (Bluesky) and DID-based (Blacksky) profile URLs
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Feat/settings page
- Add OAuth scope for settings collection to enable saving preferences
- Fix settings lexicon: use literal:self key and add maxLength to scoreLens
- Implement preferences refresh mechanism to update scores immediately after saving
- Add lexicon publishing documentation and custom skill for future updates
- Regenerate TypeScript types with proper AT Protocol schema imports
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Feature/migrate fragrances
Resolved merge conflicts by accepting main branch's atUriUtils approach:
- ExplorePage: Use batchResolveAtUris for fragrances and houses
- HousePage: Use resolveAtUri for house data
- LandingPage: Use batchResolveAtUris for fragrances and houses
- ProfilePage: Use batchResolveAtUris for house pre-fetching
- SingleReviewPage: Use resolveAtUri for fragrance and house data
The main branch's utility functions provide better performance through
batching and cleaner code through centralized caching logic.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Five compounding bugs caused intermittent/broken social link previews:
1. No fetch timeouts — all Bluesky API calls used bare fetch() with no
AbortController. When public.api.bsky.app was slow, the Cloudflare
function hung past the 5-10 s window most social bots (Discord, Slack,
Twitter) wait before giving up. Fixed with fetchWithTimeout (3 s each).
2. Sequential API calls compounded latency — for review pages the function
made up to 4 serial requests (resolve → review → fragrance → profile).
Fragrance and profile fetches are now fired in parallel with Promise.all,
cutting the round-trip count from 4 to 3.
3. Failures were cached for 1 hour — Cache-Control: max-age=3600 was set
unconditionally, including when all API calls failed and only generic
fallback tags were injected. The first bot request during any slow period
would cache the broken version for a full hour. Fallback responses now get
max-age=60 so they retry quickly; the long TTL is only set on success.
4. Review page OG image was always missing — the author avatar was
commented out, so every review share fell back to default-og.png which
doesn't exist in /public (404). Author avatar is now used, with favicon.svg
as the last-resort fallback instead of a 404 URL.
5. Minor tag issues — twitter:card was "summary" instead of
"summary_large_image" (inconsistent with client-side SEO component), and
og:url / og:type were missing from server-injected tags. Both fixed.
Note: a proper og-default.png should still be added to /public for the
edge case where a profile has no avatar.
https://claude.ai/code/session_01X8Y5NDvGS5i5unJrFZSfxw
- Fix getReviewDisplayScore to use != null check instead of truthiness,
so a stored weightedScore of 0 is not incorrectly discarded
- Restructure house stats into a two-pass calculation: first aggregate
per-fragrance averages, then average those per house. This prevents a
heavily-reviewed fragrance from skewing the house score and ensures the
house comparison reflects the quality of its catalogue, not review volume
- Revert overly restrictive avgScore > 0 guard that excluded reviews from
incomplete wear sessions
https://claude.ai/code/session_01QFnkoKuapGhLQLhmej6k3t
- ReviewCard: replace decodeWeightedScore(value.weightedScore || 0) with
getReviewDisplayScore(), which falls back to calculating from available
ratings when weightedScore is not stored. Fixes 5-empty-stars showing
on the profile review list for records missing the weightedScore field.
- ExplorePage: replace weightedScore filter with openingRating check so
all reviews (stage 1, 2, and 3) appear in the feed as they come in,
not just fully completed reviews. ReviewCard already handles score
display for partial reviews via getReviewDisplayScore().
https://claude.ai/code/session_01HKfwyhxiKbcwoJQt1EVjkZ
When logged in and viewing a review by another user, a Share button
now appears. Sharing uses "★★★★☆ — via @handle" for the stars line
instead of bare stars, and the via-attribution length is factored into
the 300-character Bluesky truncation calculation.
https://claude.ai/code/session_01A1A9S5cCnnEWqsn3Sz2xqJ
Added critical section to CLAUDE.md documenting two major bugs in
the npm run gen-api code generator:
1. Unused imports (CID, ValidationResult, BlobRef, $Typed, OmitKey)
that cause TypeScript strict mode compilation failures
2. Overwrites src/client/index.ts and removes bskySchemas import,
breaking all com.atproto.repo.* API calls
Includes step-by-step fix instructions and code examples to prevent
future Claude Code sessions from encountering these build failures.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Cleaned up code generator artifacts:
- Removed unused CID imports from multiformats/cid
- Removed unused ValidationResult and BlobRef imports
- Removed unused $Typed and OmitKey type imports
These unused imports were causing TypeScript compilation errors
in the Cloudflare Pages deployment build.
Note: npm run gen-api may reintroduce these - manual cleanup required.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Schema Changes:
- Add optional fragranceName and houseName fields to review records
- Reviews now cache fragrance metadata for durability when fragrances are deleted/renamed
- Regenerated API types from updated lexicon schema
Fragrance Deletion:
- Enable deletion even when reviews exist (with warnings)
- Show review count and impact in deletion UI
- Reviews display deleted fragrances as "Name (deleted)"
- Created EditFragrancePage component with edit/delete functionality
- Added route for /profile/:handle/fragrance/:rkey/edit
Review Editing After 24 Hours:
- Allow editing text notes at any time
- Allow changing fragrance assignment at any time
- Lock stage ratings after 24 hours
- Update validation logic to distinguish editable vs locked fields
- Show edit button for all reviews with appropriate functionality
Bug Fixes:
- Fix fragrance deletion using typed method instead of raw XRPC call
- Fix review deletion using typed method instead of raw XRPC call
- Fix edit button visibility for reviews >= 24 hours old
- Remove "or is private" from fragrance not found error (records are always public)
- Update cached fragrance metadata when editing old reviews
UI/UX Improvements:
- Show cached fragrance names in reviews when fragrance is deleted
- Updated edit UI for post-24h reviews (text and fragrance only)
- Better confirmation dialogs explaining what will change
- Clearer error messages and permissions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Install web-haptics library for mobile vibration feedback
- Create reusable Button component with context-based haptics
- 6 semantic contexts: navigation, cancel, destructive, creation, save, share
- Maps contexts to haptic patterns (success, nudge, error, custom 100ms)
- Create HapticLink wrapper for navigation links with 100ms haptic
- Add haptics to all interactive elements:
- Navigation links in Header, TabBar, and all pages
- Action buttons across all forms and pages
- Review cards and house cards
- Haptics are optional via context prop, maintaining backward compatibility
- Gracefully degrades on desktop (no errors, no vibration)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements service-agnostic URLs for share buttons, profile links, and
footer links. Detects which service users are logged in with (Bluesky,
Blacksky, etc.) and adapts all external links accordingly.
Key features:
- Service configuration system for easy addition of new services
- OAuth issuer detection for logged-in users
- DID-based service detection for profile links
- Share buttons use logged-in user's service
- Profile handles link to user's service (right-aligned with external icon)
- Blacksky uses DID-based profile URLs, Bluesky uses handle-based
- Defaults to Blacksky for logged-out/unknown users
- Comprehensive documentation for adding new services
Technical implementation:
- ServiceProvider context wraps entire app
- useShareButton hook for compose intents
- Service detection via OAuth issuer and PDS URL patterns
- Supports handle-based (Bluesky) and DID-based (Blacksky) profile URLs
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add OAuth scope for settings collection to enable saving preferences
- Fix settings lexicon: use literal:self key and add maxLength to scoreLens
- Implement preferences refresh mechanism to update scores immediately after saving
- Add lexicon publishing documentation and custom skill for future updates
- Regenerate TypeScript types with proper AT Protocol schema imports
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Resolved merge conflicts by accepting main branch's atUriUtils approach:
- ExplorePage: Use batchResolveAtUris for fragrances and houses
- HousePage: Use resolveAtUri for house data
- LandingPage: Use batchResolveAtUris for fragrances and houses
- ProfilePage: Use batchResolveAtUris for house pre-fetching
- SingleReviewPage: Use resolveAtUri for fragrance and house data
The main branch's utility functions provide better performance through
batching and cleaner code through centralized caching logic.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Five compounding bugs caused intermittent/broken social link previews:
1. No fetch timeouts — all Bluesky API calls used bare fetch() with no
AbortController. When public.api.bsky.app was slow, the Cloudflare
function hung past the 5-10 s window most social bots (Discord, Slack,
Twitter) wait before giving up. Fixed with fetchWithTimeout (3 s each).
2. Sequential API calls compounded latency — for review pages the function
made up to 4 serial requests (resolve → review → fragrance → profile).
Fragrance and profile fetches are now fired in parallel with Promise.all,
cutting the round-trip count from 4 to 3.
3. Failures were cached for 1 hour — Cache-Control: max-age=3600 was set
unconditionally, including when all API calls failed and only generic
fallback tags were injected. The first bot request during any slow period
would cache the broken version for a full hour. Fallback responses now get
max-age=60 so they retry quickly; the long TTL is only set on success.
4. Review page OG image was always missing — the author avatar was
commented out, so every review share fell back to default-og.png which
doesn't exist in /public (404). Author avatar is now used, with favicon.svg
as the last-resort fallback instead of a 404 URL.
5. Minor tag issues — twitter:card was "summary" instead of
"summary_large_image" (inconsistent with client-side SEO component), and
og:url / og:type were missing from server-injected tags. Both fixed.
Note: a proper og-default.png should still be added to /public for the
edge case where a profile has no avatar.
https://claude.ai/code/session_01X8Y5NDvGS5i5unJrFZSfxw
- Fix getReviewDisplayScore to use != null check instead of truthiness,
so a stored weightedScore of 0 is not incorrectly discarded
- Restructure house stats into a two-pass calculation: first aggregate
per-fragrance averages, then average those per house. This prevents a
heavily-reviewed fragrance from skewing the house score and ensures the
house comparison reflects the quality of its catalogue, not review volume
- Revert overly restrictive avgScore > 0 guard that excluded reviews from
incomplete wear sessions
https://claude.ai/code/session_01QFnkoKuapGhLQLhmej6k3t
- ReviewCard: replace decodeWeightedScore(value.weightedScore || 0) with
getReviewDisplayScore(), which falls back to calculating from available
ratings when weightedScore is not stored. Fixes 5-empty-stars showing
on the profile review list for records missing the weightedScore field.
- ExplorePage: replace weightedScore filter with openingRating check so
all reviews (stage 1, 2, and 3) appear in the feed as they come in,
not just fully completed reviews. ReviewCard already handles score
display for partial reviews via getReviewDisplayScore().
https://claude.ai/code/session_01HKfwyhxiKbcwoJQt1EVjkZ
When logged in and viewing a review by another user, a Share button
now appears. Sharing uses "★★★★☆ — via @handle" for the stars line
instead of bare stars, and the via-attribution length is factored into
the 300-character Bluesky truncation calculation.
https://claude.ai/code/session_01A1A9S5cCnnEWqsn3Sz2xqJ
Added critical section to CLAUDE.md documenting two major bugs in
the npm run gen-api code generator:
1. Unused imports (CID, ValidationResult, BlobRef, $Typed, OmitKey)
that cause TypeScript strict mode compilation failures
2. Overwrites src/client/index.ts and removes bskySchemas import,
breaking all com.atproto.repo.* API calls
Includes step-by-step fix instructions and code examples to prevent
future Claude Code sessions from encountering these build failures.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Cleaned up code generator artifacts:
- Removed unused CID imports from multiformats/cid
- Removed unused ValidationResult and BlobRef imports
- Removed unused $Typed and OmitKey type imports
These unused imports were causing TypeScript compilation errors
in the Cloudflare Pages deployment build.
Note: npm run gen-api may reintroduce these - manual cleanup required.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>