The service worker was causing 503 errors and freezing requests because:
- Pre-caching was trying to cache non-existent files
- Fetch handler was too aggressive with interception
- Failed cache operations blocked all requests
Fixes:
- Made pre-caching optional (logs errors but doesn't fail)
- More lenient fetch handler (only caches successful responses)
- Proper error handling for cache operations
- Only intercepts GET requests
- Falls back to network if cache fails
Enhanced the navigation bar by adding Naomi's beautiful portrait icon next to the library title.
Visual Enhancements:
- Added 40x40px circular icon in nav-brand section
- Icon features purple border matching app theme
- Subtle purple shadow for depth and cohesion
- Smooth hover animation (scale + enhanced shadow)
Styling Details:
- Border: 2px solid witch-purple (#9d4edd)
- Box shadow: Purple glow matching theme
- Hover effect: 5% scale increase with stronger shadow
- Transition: Smooth 0.2s animation
- Border radius: 50% (perfect circle)
- Object-fit: cover (maintains aspect ratio)
Layout:
- Flexbox layout for nav-brand alignment
- 0.75rem gap between icon and title
- Icon, title, and version badge all properly aligned
The navbar now has a personal, elegant touch with Naomi's portrait greeting users at the top of every page! The circular presentation with the purple border perfectly complements the witchy aesthetic while adding warmth and personality to the interface.
✨ This enhancement was created by Hikari~ 🌸
Completes PWA implementation by adding all required app icons generated from Naomi's beautiful portrait artwork.
Icon Assets:
- Source image: Stunning portrait with witchy purple background
- Features Naomi's signature look: ashen brown wavy hair, sky blue eyes, pink glasses, purple nails, vampire fangs
- Perfect match for the library's purple aesthetic!
Generated Icons:
- Standard sizes: 72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512
- Maskable variants: 192x192, 512x512 (with purple background padding for safe area)
- Favicon: 32x32 favicon.ico
Maskable Icons:
- Added purple (#9d4edd) background padding
- Ensures icon content stays within safe area on all devices
- Maintains visual consistency with app theme
Favicon:
- Replaced placeholder favicon with Naomi's portrait
- Will display in browser tabs and bookmarks
- Consistent branding across all platforms
The PWA now has a complete, cohesive visual identity using Naomi's portrait throughout! Users will see her beautiful face as the app icon on their home screens and in their browser tabs! 💜✨✨ This feature was built by Hikari~ 🌸
Implements issue #52 - adds complete PWA functionality with offline support, installability, and app-like experience.
PWA Features:
- Installable on mobile devices and desktop
- Offline support with smart caching strategies
- App-like experience with standalone display mode
- Full-screen mode and app icon on home screen
- Install prompt with beautiful banner UI
Manifest Configuration:
- Created manifest.json with app metadata
- App name: "Naomi's Library"
- Theme colour: #9d4edd (witchy purple!)
- Background colour: #1a1a2e (dark theme)
- Display mode: standalone (app-like)
- Icons: Multiple sizes specified (72-512px, maskable variants)
- Categories: entertainment, lifestyle
Service Worker Implementation:
- Cache-first strategy for static assets (JS, CSS, fonts, images)
- Network-first strategy for API requests with cache fallback
- Network-first strategy for HTML pages with cache fallback
- Cache versioning: library-v1 (static, dynamic, images)
- Automatic cache cleanup on activation
- Update checking every 60 seconds
- Message handling for cache clearing and skipWaiting
Offline Support:
- Created offline.html fallback page with beautiful UI
- Auto-retry when network comes back online
- Purple gradient matching app theme
- Informative messaging about offline state
Install Prompt:
- Created PwaInstallComponent with banner UI
- Detects installability and shows prompt
- "Install" and "Not Now" actions
- Responsive design for mobile and desktop
- Session storage for dismissal state
- Animated slide-up entrance
Services:
- PwaService: Manages service worker registration, install prompts, cache clearing
- Automatic initialization on app bootstrap
- Signals for reactivity: isInstallable, isInstalled, promptEvent
- Update detection and notification
HTML Updates:
- Added PWA meta tags (theme-color, description)
- Linked manifest.json
- Apple-specific meta tags for iOS support
- Apple touch icon specified
Build Configuration:
- Updated project.json to include PWA assets
- manifest.json, service-worker.js, offline.html copied to dist root
Integration:
- Added PwaService to app bootstrap
- Integrated PwaInstallComponent in main app template
- Install banner appears at bottom of screen
- Dismissible with session storage
Technical Notes:
- Service worker uses efficient caching strategies per resource type
- Handles Chrome extensions and non-HTTP protocols gracefully
- Supports message-based cache clearing
- Includes skipWaiting for immediate updates
- Compatible with iOS, Android, and desktop PWA standards
The library is now a fully-functional Progressive Web App that users can install on any device! The app works offline with cached content and provides an app-like experience with no browser UI in standalone mode.
Note: App icons still need to be generated/provided (placeholder paths specified in manifest).
✨ This feature was built by Hikari~ 🌸
Implements issue #56 (activity feed) followup - adds complete detail pages for all entity types so activity feed and homepage links work properly.
Created Detail Components:
- game-detail.component.ts - Shows full game details with platform, series, time played
- book-detail.component.ts - Shows book details with author, ISBN, series, reading time
- music-detail.component.ts - Shows music details with artist, type (album/single), listening time
- art-detail.component.ts - Shows art details with artist, description, large image display
- show-detail.component.ts - Shows show details with type (TV/anime/film), watch time
- manga-detail.component.ts - Shows manga details with author, reading time
Features per Detail Page:
- Read-only display of full entity information
- Cover/poster/art image display (when available)
- Rating display with 10-star system (when applicable)
- Time spent formatting (hours and minutes)
- Date formatting (British English locale)
- Tags display with entity-specific colour schemes
- External links with hover effects
- Comments section with add/edit/delete functionality
- Like button integration
- Breadcrumb navigation back to list page
- Loading states with spinner
- Error handling for not found cases
- Responsive design for all screen sizes
Routing Updates:
- Added 6 new routes: /games/:id, /books/:id, /music/:id, /art/:id, /shows/:id, /manga/:id
- All routes use lazy loading with loadComponent
Navigation Fixes:
- Fixed activity feed like links to point to entity detail pages (was pointing to list)
- Updated homepage recent items to link directly to entity detail pages (was pointing to list)
Technical Fixes:
- Corrected Art component to only use existing fields (removed non-existent medium, rating, notes)
- Fixed Show component to use coverImage instead of non-existent posterImage
- Added proper null checks for optional fields (timeSpent, dateStarted, dateFinished)
- Added non-null assertions where values are guaranteed to exist within conditionals
Each detail component follows the established pattern from the list components whilst adapting for single-item display. All components use Angular signals for reactive state management and integrate with existing services.
✨ This issue was created with help from Hikari~ 🌸
Changed comment layout to use block-level elements and fixed entity link
to navigate to the specific item instead of the list page.
Template changes:
- Wrapped icon and text in activity-comment-header div
- Changed entity link to include entityId: ['/' + entityType + 's', entityId]
- Comment preview now displays as a block element below the header
CSS changes:
- Added flex layout to activity-comment-header (inline display)
- Changed activity-comment to flex column layout (block display)
- Added left margin to comment-preview to align with content above
- Removed italic style from comment-preview
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed approach from stripping HTML on backend to rendering HTML with
sanitization on frontend, matching the pattern used in comment-display
component. This preserves HTML formatting (bold, italics, etc.) in
comment previews whilst still protecting against XSS attacks.
Backend changes:
- Reverted stripHtml() method (no longer needed)
- Keep full HTML content in commentPreview field
Frontend changes:
- Import and inject SanitizeService
- Changed from text interpolation to [innerHTML] with sanitization
- Changed <p> to <div> for comment preview container
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added stripHtml() helper method to ActivityService to remove HTML tags
from comment content before creating the preview text. This ensures that
comments display as plain text in the activity feed instead of showing
raw HTML like "<p>test</p>".
The regex pattern /<[^>]*>/g removes all HTML tags whilst preserving
the actual text content, which is then trimmed and truncated to 100
characters for the preview.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added explicit return type annotations (SuggestionActivity[], LikeActivity[],
CommentActivity[], AchievementActivity[]) to all private activity service
methods. This ensures TypeScript properly narrows the discriminated union
types based on the ActivityType enum values, resolving compilation errors
where the type system couldn't infer the specific activity subtypes.
Also added type annotation to the async map callback in getLikeActivities
to ensure the returned Promise resolves to the correct LikeActivity type.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed the activity.types export from 'export type *' to 'export *'
so that the ActivityType enum can be used as both a type and a value.
This is necessary because:
- Backend needs ActivityType.suggestion, ActivityType.like, etc. as values
- Frontend needs ActivityType enum for switch statements
- TypeScript enums need to be exported as values to be used at runtime
Changed all activity type assignments to use ActivityType enum members
(ActivityType.suggestion, ActivityType.like, etc.) instead of string
literals ('SUGGESTION', 'LIKE', etc.) to ensure proper type discrimination
in the union type.
Frontend:
- Import ActivityType as value (not just type)
- Update switch cases to use enum members
- Expose ActivityType in component for template access
Backend:
- Import ActivityType as value
- Use enum members in all activity mappings
This ensures TypeScript can properly discriminate the union type and
provides type safety throughout the activity feed.
Fixed two type errors:
- Changed ActivityType assertions from 'as ActivityType' to 'as const'
for proper literal type inference
- Changed achievement.name to achievement.title to match the actual
AchievementDefinition structure
This ensures type safety and proper union type discrimination.
Implements issue #56 with a timeline-style activity feed showing recent
user activity across the library.
Activity Types Displayed:
- Suggestions: Shows suggested items with status badges
- Likes: Shows liked content with links to items
- Comments: Shows comments with preview text
- Achievements: Shows earned achievements with icons and points
Database & Backend:
- Created ActivityService to aggregate from existing data sources
- No new database table needed (uses existing suggestions, likes, comments, achievements)
- Efficient querying with parallel data fetching
- Privacy-aware: Only shows activity from users with profilePublic: true
- Filters out banned users automatically
API Endpoints:
- GET /api/activity - Get general activity feed with pagination
- GET /api/activity/:userId - Get specific user's activity
- Query parameters: limit (max 100, default 50), offset (default 0)
- Returns: activities array, total count, hasMore flag
Frontend Activity Feed:
- Beautiful timeline layout with user avatars and badges
- Relative timestamps ("5m ago", "2h ago", "3d ago")
- Activity type icons (💡💬❤️🏆)
- Colour-coded status badges for suggestions
- Comment previews with styled quote blocks
- Achievement displays with icons and points
- Infinite scroll with "Load More" button
- Links to user profiles and content items
UI Components:
- Responsive card-based design
- User avatars with placeholder fallback
- Badge display (STAFF, MOD, VIP, primary badges)
- Entity links that route to appropriate media pages
- Clean typography and spacing
- Loading and empty states
Privacy & Performance:
- Respects profilePublic setting (existing privacy control)
- Only displays activity from non-banned users
- Pagination to prevent loading too much data
- Efficient aggregation with limit multipliers
- Clean separation of concerns (service/routes/component)
Navigation:
- Added /activity route
- Added "📰 Activity Feed" link to user dropdown menu
- Positioned between Leaderboard and About
Implementation Notes:
- Built entirely from existing data (no activity table needed)
- Retroactive: Shows all historical activity automatically
- Type-safe with full TypeScript interfaces
- Activity union type for type discrimination
- Standalone Angular component
- Clean, maintainable code structure
Completes time tracking implementation across all media types by
applying the same pattern established in Games component.
Changes Applied to Books, Music, Shows, and Manga:
UI Components:
- Added time tracking state properties (hours/minutes for new and edit)
- Integrated hour/minute input fields in Add forms (after rating)
- Integrated hour/minute input fields in Edit forms (after edit-rating)
- Added time spent display on media cards with appropriate emojis:
* Books: 📖 Reading Time
* Music: 🎵 Listening Time
* Shows: 📺 Watch Time
* Manga: 📚 Reading Time
Form Management:
- Updated resetForm() to clear time tracking fields
- Added updateNew[Type]TimeSpent() conversion methods
- Added updateEdit[Type]TimeSpent() conversion methods
- Updated startEdit() to populate time fields from stored data
Helper Methods:
- Added formatTimeSpent() to format minutes as "Xh Ym", "Xh", or "Ym"
- Converts hours/minutes to total minutes for storage
- Splits total minutes back to hours/minutes for editing
Styling:
- Added .form-row CSS for side-by-side hour/minute inputs
- Added .time-spent CSS with coloured display:
* Books: Green (#10b981)
* Music: Purple (#8b5cf6)
* Shows: Purple (#8b5cf6)
* Manga: Green (#10b981)
All implementations follow the exact same pattern for consistency
and maintainability across the application.
Implements issue #57 with manual time entry functionality:
- Added timeSpent field (stored in minutes) to all media models
- Supports hours and minutes input in forms
- Displays formatted time on media cards
Database Schema:
- Added timeSpent Int? field to Game, Book, Music, Show, and Manga models
- Stored in minutes for consistency and easy calculation
Shared Types:
- Updated all media type interfaces with timeSpent? field
- Added to CreateDto and main interfaces for all media types
Frontend (Games - template for other types):
- Added hour/minute input fields to Add and Edit forms
- Split input with validation (minutes 0-59)
- Auto-calculates total minutes on change
- Formats display as "Xh Ym", "Xh", or "Ym" as appropriate
- Green highlighted time display on cards with ⏱️ icon
- Populates edit form from existing time data
Implementation Notes:
- Backend services require no changes (DTOs handle automatically)
- Time conversion helpers for display and storage
- Form state properly resets time fields
- Edit mode correctly splits minutes back to hours/minutes
Next Steps:
- Apply same UI pattern to Books, Music, Shows, Manga
- Add time statistics to user profiles
- Consider aggregate time tracking views
Implements issue #55 with multiple leaderboard categories:
- Top Suggestions (by count and acceptance rate)
- Top Likes (by total likes given)
- Top Comments (by total comments posted)
- Overall Leaders (weighted by achievement points and engagement diversity)
Features:
- Tabbed UI with reactive state management
- Medal indicators for top 3 positions
- User avatars and badges display
- Current user highlighting
- Privacy controls via profilePublic setting
- Configurable result limits (max 100)
- Detailed statistics per category
Backend:
- Created LeaderboardService with aggregation logic
- Filters for public profiles and non-banned users
- Efficient sorting algorithms for each category
- Parallel data fetching for all leaderboards
Frontend:
- Standalone Angular component with signals
- Responsive card-based layout
- Integration with existing user profile system
- Navigation link in header dropdown
Technical notes:
- Uses Fastify AutoLoad with FastifyPluginAsync pattern
- Shared types across monorepo for type safety
- Leverages existing achievement system data
- Add series name and order display to book cards (books-list.component.ts:598-602)
- Add series name and order display to game cards (games-list.component.ts:560-564)
- Series shows as "📚 Series Name #Order" format
- Add brown-themed styling (#8b6f47) for series info
- Pre-populate series fields when editing books/games
- Series fields added to startEdit() methods for both books and games
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements series grouping functionality for books and games to allow
tracking franchises and related items.
Database changes:
- Add series (optional string) and seriesOrder (optional number) fields to Book model
- Add series (optional string) and seriesOrder (optional number) fields to Game model
Backend changes:
- Add GET /books/series/:seriesName endpoint to fetch all books in a series
- Add GET /games/series/:seriesName endpoint to fetch all games in a series
- Add getBooksBySeries() method to BookService (orders by seriesOrder asc)
- Add getGamesBySeries() method to GameService (orders by seriesOrder asc)
- Create/Update endpoints automatically handle series fields via DTOs
Frontend changes:
- Add series and seriesOrder input fields to "Add New Book" form
- Add series and seriesOrder input fields to "Edit Book" form
- Add series and seriesOrder input fields to "Add New Game" form
- Add series and seriesOrder input fields to "Edit Game" form
Type definitions:
- Update Book and CreateBookDto interfaces with series fields
- Update Game and CreateGameDto interfaces with series fields
Closes#54
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements comprehensive About page (#51) with the following sections:
- Purpose: Explains that this is Naomi's curated collection
- Features: Overview of all 6 media types (Books, Games, Manga, Shows, Music, Art)
- How to Use: 5-step guide for browsing, liking, commenting, suggesting, and earning achievements
- Technology Stack: Details Angular, Fastify, MongoDB, Prisma, and other technologies
- Credits: Acknowledges Hikari as the developer and Naomi as the visionary
- Contact & Support: Links to Gitea, email, website, and Discord community
- Version Information: Current version and licence details
Technical implementation:
- New AboutComponent with responsive design
- Added /about route to app routes
- Added "ℹ️ About" link to user dropdown menu
- Beautiful purple gradient styling matching the witchy aesthetic
- FontAwesome icons for visual appeal
- Fully responsive for all device sizes
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Summary
This PR implements comprehensive user profile enhancements including:
- User profile pages showing stats, badges, social links, and bio
- Achievement system with 62 achievements across 5 categories
- Primary badge selection allowing users to display their preferred badge
- Admin profile editing capabilities
## Changes
### User Profiles (#45)
- **Frontend**: User profile pages with stats display
- Profile cards showing avatar, display name, username, and bio
- Social links section (Website, GitHub, Bluesky, LinkedIn, Twitch, YouTube, Discord)
- Stats display (suggestions, accepted suggestions, likes, comments)
- Recent achievements section
- Badge display
- Report button for other users' profiles
- **Backend**: Profile API endpoints
- Get user profile by username or ID
- Profile includes stats, badges, and achievement points
### Achievement System (#48)
- **Database**: UserAchievement model for tracking progress
- **62 Total Achievements** across 5 categories:
- **Suggestions (15)**: First suggestion through ultimate curator
- **Likes (12)**: First like through legendary fan
- **Comments (12)**: First comment through review legend
- **Engagement (15)**: Login streaks and activity milestones
- **Reports (8)**: Valid reports and accuracy tracking
- **Backend**: AchievementService with real-time checking
- Integrated into all user interaction points
- API endpoints for achievement data
- Progress tracking to avoid recalculation
- **Frontend**: Achievements page and profile integration
- Full achievements page with category filtering
- Tier-based styling (Bronze, Silver, Gold, Platinum, Diamond)
- Progress indicators for in-progress achievements
- Recent achievements on profile pages
### Primary Badge System (#49)
- **Database**: Add primaryBadge field to User model
- **Backend**: Update profile endpoints to include primary badge
- **Frontend**: Primary badge selection in settings
- Only shows badges the user has earned
- Displayed on profile page
- Displayed in comments (next to username)
- Falls back to no badge if selection is invalid
- **Admin Features**: Admin can edit any user's primary badge
### Admin Enhancements
- Comprehensive profile editing modal for admins
- Edit display name, bio, slug, social links
- Set primary badge for users
- Visual feedback for save/error states
- Admin action buttons in report review modals
- Ban user, delete comment, edit profile
- Integrated with report workflow
### Quality Improvements
- Improved dropdown option contrast for readability
- Hide all badges when no primary badge is selected
- "View All" achievements link only shown on own profile
- Improved achievement text readability
## Testing
- ✅ User profiles display correctly with stats and badges
- ✅ Achievement checking works for all interaction types
- ✅ Primary badge selection persists and displays correctly
- ✅ Admin profile editing saves successfully
- ✅ Report workflow integrated with admin actions
- ✅ Achievements page shows all 62 achievements with filtering
- ✅ Text readability improved across components
Closes#45Closes#48Closes#49
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #58
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
## Summary
This PR implements several improvements to the library application:
- Added start and finish date tracking for media items
- Added "Retired" category for abandoned media
- Implemented avatar-based user menu with dropdown navigation
- Added automatic background token refresh to prevent session expiry
- Created centralised logging system with frontend-to-API log forwarding
- Added toast notifications for error handling
## Changes
### Media Tracking (#41)
- Added `dateStarted` and `dateFinished` fields to Books, Games, Manga, Music, and Shows
- Updated TypeScript types, Prisma schema, and API services
- Added manual date input fields to frontend forms
- Properly converts HTML date strings to Date objects before API submission
### Retired Category (#43)
- Added `RETIRED` status to all media type enums
- Updated Prisma schema, frontend dropdowns, and filter buttons
- Added status label handling for retired items
### User Menu (#46)
- Replaced username text with avatar image in header
- Created dropdown menu with navigation items (Users, Audit, Suggestions)
- Added logout button to menu
- Implemented keyboard accessibility (tabindex, role, keyup handlers)
### Token Refresh (#44)
- Implemented automatic token refresh every 13 minutes in background
- Added proactive refresh to prevent token expiry during form filling
- Prevents users from losing form data due to expired sessions
### Centralised Logging (#1)
- Created `/log` endpoint on API to receive frontend logs
- Replaced API console.log calls with @nhcarrigan/logger
- Created ConsoleLoggerService to intercept all console methods on frontend
- Added global error handlers (window.error, unhandledrejection) on frontend
- Added process error handlers (uncaughtException, unhandledRejection, SIGTERM, SIGINT) on API
- All frontend console activity now forwarded to centralised logging
### Error Handling
- Created ToastService and ToastComponent for displaying errors
- Integrated with GlobalErrorHandler and HTTP interceptor
- Added accessibility features (keyboard navigation, ARIA attributes)
- Set toast opacity to 40% for optimal readability
### Testing & Build
- Fixed pre-existing test failure for GET / route (now returns version info)
- Added ESM module mocking (jsdom, marked, dompurify, @nhcarrigan/logger)
- Configured Jest with isolatedModules to handle TypeScript errors
- Excluded test-setup.ts from production build
- All tests passing (123 total)
- Build passing with no errors
## Test Plan
- [x] All tests pass (123 tests)
- [x] Build passes without errors
- [x] Lint passes (only pre-existing warnings)
- [x] Date fields work correctly on all media types
- [x] Retired status displays and filters properly
- [x] Avatar menu opens/closes correctly with keyboard and mouse
- [x] Token refresh prevents session expiry
- [x] Toast notifications appear for errors
- [x] Frontend logs forward to API successfully
- [x] Root route returns version information
Closes#41Closes#43Closes#44Closes#46Closes#1🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #50
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>