` elements in the flex-row-reverse icon container (line 33) with grid/list icons (PrimeVue pi-th, pi-list).
+ _Verification commands:_
+ - `npm run check`
+ - `npm run dev` (visual inspection - buttons appear in icon row)
+ _Notes:_ Follow existing icon pattern: `shrink-0 px-3 cursor-pointer text-muted-color inline-block transform duration-300 hover:scale-150 hover:text-color`.
+
+- [ ] T-005-17 – Add click handlers to toggle buttons in AlbumHero.vue (FR-005-03, S-005-01, S-005-02).
+ _Intent:_ Implement @click handlers that call `lycheeStore.setAlbumViewMode('grid' | 'list')`.
+ _Verification commands:_
+ - `npm run check`
+ - `npm run dev` (click buttons, verify view switches, localStorage updated)
+ _Notes:_ Import LycheeState store at top of component.
+
+- [ ] T-005-18 – Add active state styling to toggle buttons in AlbumHero.vue (FR-005-03, UI-005-01, UI-005-02).
+ _Intent:_ Apply different styling when button is active (current view mode), e.g., different color or text-primary-emphasis.
+ _Verification commands:_
+ - `npm run dev` (visual inspection - active button highlighted)
+ _Notes:_ Use computed property or v-bind:class based on current view mode.
+
+- [ ] T-005-19 – Add aria-labels and aria-pressed to toggle buttons (NFR-005-03, UI-005-03).
+ _Intent:_ Add accessibility attributes: aria-label="Switch to grid view" / "Switch to list view", aria-pressed based on active state.
+ _Verification commands:_
+ - `npm run check`
+ - Manual accessibility audit (keyboard navigation, screen reader)
+ _Notes:_ Ensure buttons are keyboard-navigable (Tab to focus, Enter to activate).
+
+- [ ] T-005-20 – Add tooltips to toggle buttons in AlbumHero.vue (FR-005-03).
+ _Intent:_ Add v-tooltip.bottom directives similar to other icons in AlbumHero.vue.
+ _Verification commands:_
+ - `npm run dev` (hover over buttons, verify tooltips appear)
+ _Notes:_ Tooltip text: "Grid view" / "List view".
+
+---
+
+### Increment I6 – Responsive Mobile Layout Testing
+
+- [ ] T-005-21 – Test list view on 320px viewport (FR-005-06, S-005-09).
+ _Intent:_ Verify layout doesn't overflow, thumbnails scale to 48px, album names wrap, counts display compactly.
+ _Verification commands:_
+ - `npm run dev` (browser DevTools responsive mode, set to 320px width)
+ _Notes:_ Make CSS adjustments if needed, use Tailwind md: breakpoints.
+
+- [ ] T-005-22 – Test list view on 375px and 768px viewports (FR-005-06, S-005-09, UI-005-05).
+ _Intent:_ Verify responsive behavior at common mobile breakpoints.
+ _Verification commands:_
+ - `npm run dev` (test multiple viewport sizes)
+ _Notes:_ Capture screenshots for documentation.
+
+- [ ] T-005-23 – Test toggle buttons on mobile viewports (FR-005-03, S-005-09).
+ _Intent:_ Verify toggle buttons remain usable and don't crowd header on mobile.
+ _Verification commands:_
+ - `npm run dev` (mobile testing)
+ _Notes:_ Consider icon-only display on very narrow screens if needed.
+
+---
+
+### Increment I7 – Component Unit Tests
+
+- [ ] T-005-24 – Create fixture file albums-list-view.json (FX-005-01).
+ _Intent:_ Create `resources/js/components/gallery/albumModule/__tests__/fixtures/albums-list-view.json` with sample album data (long names, 0 counts, badges, high counts).
+ _Verification commands:_
+ - File exists and is valid JSON
+ _Notes:_ Include edge cases mentioned in spec.
+
+- [ ] T-005-25 – Write unit tests for AlbumListItem component (S-005-05, S-005-06, S-005-07, S-005-08).
+ _Intent:_ Test rendering with various album data scenarios, navigation behavior, badge display.
+ _Verification commands:_
+ - `npm run check`
+ _Notes:_ Use Vue Test Utils, test props passing and rendering output.
+
+- [ ] T-005-26 – Write unit tests for AlbumListView component (S-005-06).
+ _Intent:_ Test rendering multiple albums, empty state, props passing.
+ _Verification commands:_
+ - `npm run check`
+ _Notes:_ Verify correct number of AlbumListItem components rendered.
+
+- [ ] T-005-27 – Write integration tests for view mode toggle (S-005-01, S-005-02, S-005-03, S-005-04).
+ _Intent:_ Test end-to-end toggle behavior, localStorage persistence, default value.
+ _Verification commands:_
+ - `npm run check`
+ _Notes:_ Mock localStorage for tests, test both available and unavailable scenarios.
+
+---
+
+### Increment I8 – Integration Testing & Visual Regression
+
+- [ ] T-005-28 – Manual integration testing: toggle between views (S-005-01, S-005-02).
+ _Intent:_ Load album page, click list toggle, verify switch, click grid toggle, verify switch back.
+ _Verification commands:_
+ - `npm run dev` (manual testing)
+ _Notes:_ Test with real album data, various album counts.
+
+- [ ] T-005-29 – Manual integration testing: localStorage persistence (S-005-04, S-005-10).
+ _Intent:_ Set view to list, reload page, verify still in list view. Test private browsing mode (defaults to grid on reload).
+ _Verification commands:_
+ - `npm run dev` (manual testing with page reloads)
+ _Notes:_ Clear localStorage between tests to verify default behavior.
+
+- [ ] T-005-30 – Keyboard accessibility testing (NFR-005-03, UI-005-03).
+ _Intent:_ Tab to toggle buttons, verify focus outline, press Enter to activate, verify view switches.
+ _Verification commands:_
+ - Manual keyboard navigation testing
+ _Notes:_ Test with screen reader if available, verify aria-labels announced.
+
+- [ ] T-005-31 – Visual regression baseline capture (optional, if tooling available).
+ _Intent:_ Capture screenshots of grid view (desktop), list view (desktop), list view (mobile) as baseline images.
+ _Verification commands:_
+ - Visual regression tool commands
+ _Notes:_ Store baselines for future regression testing.
+
+- [ ] T-005-32 – Performance testing with 100 albums (NFR-005-02).
+ _Intent:_ Load album with 100+ albums, measure rendering time, verify < 300ms for list view.
+ _Verification commands:_
+ - Browser DevTools Performance tab
+ _Notes:_ Compare grid vs list rendering performance.
+
+---
+
+### Increment I9 – Documentation Updates
+
+- [ ] T-005-33 – Update knowledge-map.md with new components.
+ _Intent:_ Add entries for AlbumListView.vue, AlbumListItem.vue, note modifications to AlbumHero.vue and LycheeState.ts.
+ _Verification commands:_
+ - File updated and readable
+ _Notes:_ Follow existing knowledge map format.
+
+- [ ] T-005-34 – Update spec.md status to Implemented.
+ _Intent:_ Change status field in spec.md from "Draft" to "Implemented", update last updated date.
+ _Verification commands:_
+ - File updated
+ _Notes:_ Update after all other tasks complete.
+
+- [ ] T-005-35 – Update roadmap.md feature status.
+ _Intent:_ Change Feature 005 status from "Planning" to "In Progress" when implementation starts, "Complete" when done.
+ _Verification commands:_
+ - File updated
+ _Notes:_ Update incrementally as progress is made.
+
+- [ ] T-005-36 – Create PR description with screenshots.
+ _Intent:_ Document feature summary, add screenshots showing grid vs list views, testing notes.
+ _Verification commands:_
+ - PR description complete
+ _Notes:_ Include before/after screenshots (desktop and mobile).
+
+---
+
+## Notes / TODOs
+
+**Environment setup:**
+- Ensure Node.js and npm are up to date
+- Run `npm install` to install dependencies before starting
+
+**Testing strategy:**
+- Prefer unit tests for components (fast feedback)
+- Manual integration testing for user flows
+- Visual regression testing optional (if tooling exists)
+
+**Deferred items (out of scope for Feature 005):**
+- Virtualization for 1000+ albums (performance optimization)
+- Backend persistence of view preference (multi-device sync)
+- Sortable columns in list view
+- Customizable column layout
+- Per-album view preferences
+
+**Common commands:**
+- `npm run format` - Format frontend code (Prettier)
+- `npm run check` - Run frontend tests and TypeScript type checking
+- `npm run dev` - Start local development server
+
+**Potential blockers:**
+- If PrimeVue icons (pi-th, pi-list) are not available, choose alternative icons
+- If localStorage is restricted (CSP, privacy settings), feature will default to grid view (acceptable)
+- If performance with 100+ albums is poor, may need virtualization (defer to follow-up)
+
+---
+
+_Last updated: 2026-01-03_
diff --git a/docs/specs/4-architecture/features/006-photo-rating-filter/plan.md b/docs/specs/4-architecture/features/006-photo-rating-filter/plan.md
new file mode 100644
index 00000000000..00abddca70b
--- /dev/null
+++ b/docs/specs/4-architecture/features/006-photo-rating-filter/plan.md
@@ -0,0 +1,577 @@
+# Feature Plan 006 – Photo Star Rating Filter
+
+_Linked specification:_ `docs/specs/4-architecture/features/006-photo-rating-filter/spec.md`
+_Status:_ Draft
+_Last updated:_ 2026-01-03
+
+> Guardrail: Keep this plan traceable back to the governing spec. Reference FR/NFR/Scenario IDs from `spec.md` where relevant, log any new high- or medium-impact questions in [docs/specs/4-architecture/open-questions.md](docs/specs/4-architecture/open-questions.md), and assume clarifications are resolved only when the spec's normative sections (requirements/NFR/behaviour/telemetry) and, where applicable, ADRs under `docs/specs/5-decisions/` have been updated.
+
+## Vision & Success Criteria
+
+**User Value:**
+Users can quickly filter photos in an album by minimum star rating threshold using an intuitive visual interface (5 clickable stars). This makes it easy to find highly-rated photos or explore photos by rating quality without complex UI. The filter only appears when relevant (at least one rated photo exists), keeping the interface clean.
+
+**Success Signals:**
+- Star filter control visible when album has ≥1 rated photo, hidden otherwise
+- Clicking star N filters photos to show rating ≥ N (minimum threshold)
+- Clicking same star again clears filter (toggle behavior)
+- Filter state persists in Pinia store during session
+- Filtering is instant (client-side, no API calls)
+- Responsive keyboard navigation works (Tab, Arrow keys, Enter)
+
+**Quality Bars:**
+- Code follows Vue 3 Composition API and TypeScript conventions (NFR-006-03)
+- Keyboard accessible with proper aria-labels (NFR-006-02)
+- Filtering performance handles 1000+ photos smoothly (<100ms) (NFR-006-04)
+- No API calls, fully client-side filtering (NFR-006-01)
+
+## Scope Alignment
+
+**In scope:**
+- Star filter control in PhotoThumbPanelControl.vue (5 clickable stars)
+- Conditional rendering (show only when rated photos exist)
+- Minimum threshold filtering logic (≥ N stars)
+- Toggle behavior (click same star to clear filter)
+- Filter state in PhotosState store (session-only persistence)
+- Client-side filtering computed property
+- Keyboard accessibility (Tab, Arrow keys, Enter)
+- Visual feedback (filled/empty stars, hover states)
+- Responsive design for mobile
+
+**Out of scope:**
+- Backend API filtering or query parameters
+- Exact rating match filtering (show only N-star photos)
+- Filtering for unrated photos explicitly
+- Multiple rating selection (checkboxes, multi-select)
+- LocalStorage or URL query parameter persistence
+- Filter controls for albums (feature is photo-only)
+- Range sliders or complex UI controls
+- Filtering by aggregate rating (average rating from all users)
+
+## Dependencies & Interfaces
+
+**Frontend Dependencies:**
+- Vue 3 (Composition API)
+- TypeScript
+- Tailwind CSS for styling
+- PrimeVue for star icons (`pi-star`, `pi-star-fill`)
+- PhotosState.ts store (state management)
+- Existing Photo model types (PhotoResource with user_rating field)
+
+**Feature Dependencies:**
+- **Feature 001 (Photo Star Rating):** This feature depends on Feature 001 being implemented. Photos must have `user_rating` field populated.
+
+**Components:**
+- PhotoThumbPanelControl.vue (existing - will be modified)
+- PhotosState.ts (existing - will be modified)
+- PhotoThumbPanel.vue or parent component (may need modification for filtered list)
+
+**Interfaces:**
+- Photo data structure from PhotosState.ts (id, user_rating: null | 0-5)
+
+**Testing Infrastructure:**
+- Vitest (component tests)
+- Vue Test Utils
+- Performance testing utilities (for 1000+ photo filtering)
+
+## Assumptions & Risks
+
+**Assumptions:**
+- Feature 001 (Photo Star Rating) is complete and user_rating field is available
+- PhotosState store exists and can be extended with filter state
+- Photo grid components accept filtered photo array via props or computed property
+- PrimeVue star icons (`pi-star`, `pi-star-fill`) are available
+- User ratings are stored as integers 1-5 (or null for unrated)
+
+**Risks / Mitigations:**
+
+| Risk | Impact | Mitigation |
+|------|--------|-----------|
+| Feature 001 not complete | High - blocking dependency | Verify Feature 001 status before starting. If incomplete, defer Feature 006 or implement mock data for testing |
+| Performance issues with 1000+ photos | Medium - slow filtering | Use Vue computed properties (cached), test with large datasets, consider virtualization if needed (defer to follow-up) |
+| Star icon visual clarity | Low - UX concern | Use standard PrimeVue icons, test with users, adjust colors/size if needed |
+| Mobile touch targets too small | Low - accessibility | Ensure stars are ≥44px touch targets, test on real devices |
+| Filter state conflicts with other features | Low - state management | Use unique state property name, follow existing patterns (e.g., NSFW visibility) |
+
+## Implementation Drift Gate
+
+**Drift Detection Strategy:**
+- Before each increment, verify the specification FR/NFR requirements still match the planned work
+- After each increment, confirm deliverables align with success criteria
+- Record any deviations or clarifications in this plan's appendix
+
+**Evidence Collection:**
+- Component tests pass (`npm run check`)
+- Visual screenshots of star filter (empty, partial filled, hover states)
+- Performance measurements (filtering time for 1000 photos)
+- Accessibility audit results (keyboard navigation, aria-labels)
+
+**Commands to Rerun:**
+- `npm run format` - Frontend formatting
+- `npm run check` - Frontend tests and TypeScript type checking
+- `npm run dev` - Local development server for manual testing
+
+## Increment Map
+
+### I1 – PhotosState Store Modifications (Filter State)
+
+**Goal:** Add photo rating filter state to PhotosState.ts
+
+**Preconditions:** PhotosState.ts store exists (verify location)
+
+**Steps:**
+1. Read existing PhotosState.ts to understand structure
+2. Add `photo_rating_filter: null | 1 | 2 | 3 | 4 | 5` property (default: null)
+3. Add computed getter `photoRatingFilter`
+4. Add action `setPhotoRatingFilter(rating: null | 1 | 2 | 3 | 4 | 5)` that updates state
+5. Write unit test for filter state behavior
+
+**Commands:**
+- `npm run check` (verify TypeScript types and tests)
+
+**Exit:** PhotosState has photo_rating_filter state, getter, and action. Tests pass.
+
+**Implements:** FR-006-04, S-006-08
+
+---
+
+### I2 – Filtering Logic Computed Property
+
+**Goal:** Create computed property that filters photos by minimum rating threshold
+
+**Preconditions:** I1 complete (filter state ready), Feature 001 complete (user_rating field exists)
+
+**Steps:**
+1. Identify where photo list is rendered (likely PhotoThumbPanel.vue or parent)
+2. Add computed property `filteredPhotos`:
+ ```typescript
+ const filteredPhotos = computed(() => {
+ const filter = photosStore.photoRatingFilter;
+ const hasRated = photos.value.some(p => p.user_rating && p.user_rating > 0);
+
+ if (filter === null || !hasRated) {
+ return photos.value;
+ }
+
+ return photos.value.filter(p =>
+ p.user_rating !== null &&
+ p.user_rating >= filter
+ );
+ });
+ ```
+3. Update photo grid rendering to use `filteredPhotos` instead of `photos`
+4. Write unit tests for filtering logic:
+ - Test filter === null → all photos
+ - Test filter === 3 → only photos with rating ≥ 3
+ - Test filter === 5 → only 5-star photos
+ - Test no rated photos → all photos shown
+ - Test unrated photos excluded when filter active
+
+**Commands:**
+- `npm run check` (tests + types)
+- `npm run dev` (manual testing with mock photo data)
+
+**Exit:** Filtered photo list works correctly based on filter state, tests pass
+
+**Implements:** FR-006-02, FR-006-05, NFR-006-01, NFR-006-04, S-006-03, S-006-04, S-006-05, S-006-07
+
+---
+
+### I3 – Star Filter Control Component Structure
+
+**Goal:** Add star filter UI control to PhotoThumbPanelControl.vue
+
+**Preconditions:** I1, I2 complete (filter state and logic ready)
+
+**Steps:**
+1. Read existing PhotoThumbPanelControl.vue to understand layout
+2. Add computed property `hasRatedPhotos`:
+ ```typescript
+ const hasRatedPhotos = computed(() =>
+ photos.value.some(p => p.user_rating && p.user_rating > 0)
+ );
+ ```
+3. Add template section for star filter (before layout buttons):
+ ```vue
+
+
+
+ ```
+4. Add methods:
+ - `handleStarClick(star: number)`: toggle logic
+ - `starIconClass(star: number)`: filled vs empty icon
+5. Import PhotosState store to access filter state
+
+**Commands:**
+- `npm run check`
+- `npm run dev` (visual inspection)
+
+**Exit:** Star filter control renders when hasRatedPhotos === true, hidden otherwise
+
+**Implements:** FR-006-01, FR-006-07, S-006-01, S-006-02
+
+---
+
+### I4 – Star Click Interaction (Toggle Behavior)
+
+**Goal:** Implement click handling for star filter with toggle logic
+
+**Preconditions:** I3 complete (star UI rendered)
+
+**Steps:**
+1. Implement `handleStarClick(star: number)` method:
+ ```typescript
+ const handleStarClick = (star: number) => {
+ const current = photosStore.photoRatingFilter;
+ if (current === star) {
+ // Click same star → clear filter
+ photosStore.setPhotoRatingFilter(null);
+ } else {
+ // Click different star → set filter
+ photosStore.setPhotoRatingFilter(star);
+ }
+ };
+ ```
+2. Implement `starIconClass(star: number)` method:
+ ```typescript
+ const starIconClass = (star: number) => {
+ const filter = photosStore.photoRatingFilter;
+ const filled = filter !== null && star <= filter;
+ return filled
+ ? 'pi pi-star-fill text-yellow-500'
+ : 'pi pi-star text-gray-300 dark:text-gray-600';
+ };
+ ```
+3. Test click behavior manually:
+ - Click star 3 → stars 1-3 filled, photos filtered
+ - Click star 3 again → all stars empty, filter cleared
+ - Click star 5 → all stars filled, only 5-star photos shown
+
+**Commands:**
+- `npm run check`
+- `npm run dev` (manual testing)
+
+**Exit:** Star clicks toggle filter correctly, visual feedback works
+
+**Implements:** FR-006-02, FR-006-03, FR-006-06, S-006-03, S-006-06, S-006-07
+
+---
+
+### I5 – Hover and Visual Feedback
+
+**Goal:** Add hover states and visual polish to star filter
+
+**Preconditions:** I4 complete (click behavior works)
+
+**Steps:**
+1. Add hover styling to star buttons:
+ - Hover effect: `hover:text-yellow-400` for empty stars
+ - Hover effect: `hover:scale-110 transition-transform duration-150`
+2. Add focus styling for keyboard navigation:
+ - Focus outline: `focus:outline-none focus:ring-2 focus:ring-primary`
+3. Test hover states:
+ - Hover over empty star → color preview
+ - Hover over filled star → maintain filled color
+4. Ensure touch targets are ≥44px on mobile:
+ - Add padding if needed: `p-2` or `p-3`
+
+**Commands:**
+- `npm run dev` (visual inspection, hover testing)
+
+**Exit:** Hover states work correctly, visual feedback is clear
+
+**Implements:** FR-006-06, UI-006-04
+
+---
+
+### I6 – Keyboard Accessibility
+
+**Goal:** Add keyboard navigation and ARIA attributes for accessibility
+
+**Preconditions:** I5 complete (visual feedback works)
+
+**Steps:**
+1. Verify ARIA attributes are correct:
+ - Group: `role="group" aria-label="Filter by star rating"`
+ - Buttons: `aria-label="Filter by N stars or higher"` and `aria-pressed="true|false"`
+2. Add keyboard event handlers:
+ - Arrow Left/Right to navigate between stars
+ - Enter/Space to activate star (same as click)
+ - Tab to focus into/out of star group
+3. Implement keyboard navigation logic:
+ ```typescript
+ const handleKeyDown = (event: KeyboardEvent, star: number) => {
+ if (event.key === 'ArrowRight' && star < 5) {
+ // Focus next star
+ focusStar(star + 1);
+ } else if (event.key === 'ArrowLeft' && star > 1) {
+ // Focus previous star
+ focusStar(star - 1);
+ } else if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ handleStarClick(star);
+ }
+ };
+ ```
+4. Test keyboard navigation:
+ - Tab to star filter
+ - Arrow keys to navigate
+ - Enter to select
+ - Tab out to layout buttons
+
+**Commands:**
+- `npm run check`
+- Manual keyboard testing
+
+**Exit:** Keyboard navigation works, ARIA attributes correct, screen reader friendly
+
+**Implements:** NFR-006-02, UI-006-05
+
+---
+
+### I7 – Responsive Mobile Layout
+
+**Goal:** Ensure star filter works on mobile devices
+
+**Preconditions:** I6 complete (full desktop functionality)
+
+**Steps:**
+1. Test on mobile viewport sizes:
+ - 320px (very narrow)
+ - 375px (iPhone SE)
+ - 768px (tablet)
+2. Verify touch targets are ≥44px:
+ - Add padding if needed: `p-2` on mobile (`md:p-1` on desktop)
+3. Adjust spacing for mobile:
+ - Star gap: `gap-1` on mobile, `gap-2` on desktop
+ - Border separator may need adjustment
+4. Test touch interaction:
+ - Tap star to filter
+ - Ensure no hover state interferes with touch
+5. Consider icon-only on very narrow screens (optional)
+
+**Commands:**
+- `npm run dev` (test in browser DevTools responsive mode)
+
+**Exit:** Star filter works on all mobile breakpoints, touch targets adequate
+
+**Implements:** Responsive design requirements
+
+---
+
+### I8 – Component Unit Tests
+
+**Goal:** Add comprehensive unit tests for star filter functionality
+
+**Preconditions:** I4 complete (core functionality works)
+
+**Steps:**
+1. Create test file for PhotoThumbPanelControl (if not exists)
+2. Write tests for `hasRatedPhotos` computed property:
+ - No rated photos → false
+ - At least one rated photo → true
+3. Write tests for star click behavior:
+ - Click star N → filter set to N
+ - Click star N when filter === N → filter cleared
+4. Write tests for filtering logic (from I2):
+ - Filter null → all photos
+ - Filter 3 → only ≥3 star photos
+ - Filter 5 → only 5-star photos
+ - No rated photos → no filtering applied
+5. Write tests for starIconClass:
+ - Filter null → all empty stars
+ - Filter 3 → stars 1-3 filled, 4-5 empty
+6. Create fixture file `photos-rating-filter.json` with sample data
+
+**Commands:**
+- `npm run check` (run all tests)
+
+**Exit:** All unit tests pass, coverage for filter functionality
+
+**Implements:** Test strategy from spec, S-006-01 through S-006-10
+
+---
+
+### I9 – Performance Testing
+
+**Goal:** Verify filtering performance with 1000+ photos
+
+**Preconditions:** I2 complete (filtering logic implemented)
+
+**Steps:**
+1. Create test dataset with 1000 photos (various ratings)
+2. Measure filtering performance:
+ - Use browser DevTools Performance tab
+ - Measure computed property recalculation time
+ - Target: <100ms for 1000 photos (NFR-006-04)
+3. Test scenarios:
+ - Filter from null to 3 (large change)
+ - Filter from 3 to 4 (small change)
+ - Clear filter (back to all photos)
+4. If performance is poor:
+ - Check for unnecessary re-renders
+ - Verify computed property is cached correctly
+ - Consider optimization (memoization, virtualization)
+
+**Commands:**
+- `npm run dev` (manual performance testing)
+- Browser DevTools Performance profiling
+
+**Exit:** Filtering completes within 100ms for 1000 photos
+
+**Implements:** NFR-006-04
+
+---
+
+### I10 – Integration Testing & Edge Cases
+
+**Goal:** Test end-to-end filter behavior and edge cases
+
+**Preconditions:** I8 complete (unit tests pass)
+
+**Steps:**
+1. Manual integration testing:
+ - Load album with mixed rated/unrated photos
+ - Verify filter appears
+ - Click stars, verify filtering works
+ - Navigate away and back, verify filter state persists in session
+ - Reload page, verify filter resets to null
+2. Test edge cases:
+ - Album with no rated photos → filter hidden
+ - Album with all same rating (e.g., all 3 stars) → filter ≥4 shows empty grid
+ - User rates photo while filter active → list updates reactively
+ - User changes photo rating → filtered list updates
+3. Test with Feature 001 integration:
+ - Verify user_rating field is populated correctly
+ - Test rating a photo, then filtering by that rating
+
+**Commands:**
+- `npm run dev` (manual testing)
+
+**Exit:** All integration tests pass, edge cases handled correctly
+
+**Implements:** S-006-08, S-006-09, S-006-10
+
+---
+
+### I11 – Documentation Updates
+
+**Goal:** Update knowledge map and spec documentation
+
+**Preconditions:** I10 complete (feature fully implemented and tested)
+
+**Steps:**
+1. Update [docs/specs/4-architecture/knowledge-map.md](docs/specs/4-architecture/knowledge-map.md):
+ - Note PhotoThumbPanelControl.vue modifications (star filter)
+ - Note PhotosState.ts modifications (filter state)
+ - Document filtering logic location
+2. Update spec.md status to "Implemented"
+3. Update roadmap.md feature status to "Complete"
+4. Create PR description with:
+ - Feature summary
+ - Screenshots (filter empty, filter active, hover states)
+ - Testing notes
+ - Dependency note (Feature 001 required)
+
+**Commands:**
+- None (documentation updates)
+
+**Exit:** Knowledge map updated, documentation current
+
+**Implements:** Documentation deliverables from spec
+
+---
+
+## Scenario Tracking
+
+| Scenario ID | Increment / Task reference | Notes |
+|-------------|---------------------------|-------|
+| S-006-01 | I3 | No rated photos → filter hidden |
+| S-006-02 | I3 | ≥1 rated photo → filter visible |
+| S-006-03 | I2, I4 | Click star 3 → filter ≥3, photos filtered |
+| S-006-04 | I2, I4 | Click star 5 → filter ≥5, only 5-star photos |
+| S-006-05 | I2 | Click star 1 → filter ≥1, all rated photos (excludes unrated) |
+| S-006-06 | I4 | Click star when already selected → filter cleared |
+| S-006-07 | I4 | Click star 4, then star 2 → filter changes to ≥2 |
+| S-006-08 | I1, I10 | Navigate within album → filter state persists |
+| S-006-09 | I10 | Reload page → filter resets |
+| S-006-10 | I10 | Rate photo while filter active → list updates reactively |
+
+## Analysis Gate
+
+**Status:** Not yet executed (spec just created)
+
+**Checklist to complete before implementation:**
+- [ ] Verify Feature 001 (Photo Star Rating) is complete and deployed
+- [ ] Confirm PhotosState store exists and can be extended
+- [ ] Check that PhotoResource includes user_rating field
+- [ ] Verify PrimeVue star icons are available (`pi-star`, `pi-star-fill`)
+- [ ] Review existing PhotoThumbPanelControl.vue structure
+- [ ] Confirm no conflicts with other active features (Feature 005)
+
+**Findings:** (To be filled after analysis gate execution)
+
+## Exit Criteria
+
+Before declaring Feature 006 complete, the following must pass:
+
+- [ ] All increments (I1-I11) completed successfully
+- [ ] `npm run format` passes (frontend code formatting)
+- [ ] `npm run check` passes (frontend tests and TypeScript type checking)
+- [ ] Manual testing confirms:
+ - [ ] Star filter hidden when no rated photos
+ - [ ] Star filter visible when ≥1 rated photo exists
+ - [ ] Click star N → photos filtered to rating ≥ N
+ - [ ] Click same star → filter cleared
+ - [ ] Hover states work correctly
+ - [ ] Keyboard navigation works (Tab, Arrow keys, Enter)
+ - [ ] Mobile responsive layout works
+ - [ ] Filter state persists during session
+ - [ ] Filter resets on page reload
+- [ ] Performance test passes (1000 photos filtered in <100ms)
+- [ ] Accessibility audit passes (aria-labels, keyboard navigation)
+- [ ] Integration with Feature 001 works (user_rating field populated)
+- [ ] Documentation updated (knowledge map, spec status)
+
+## Follow-ups / Backlog
+
+**Potential enhancements (defer to future features):**
+- Exact rating match filter (show only 3-star photos, not ≥3)
+- Explicit "Unrated" filter option (show only unrated photos)
+- Multi-select rating filter (checkboxes for 4 AND 5 stars)
+- Combined filters (rating + date range + tags + NSFW)
+- Save filter presets (named filters)
+- URL query parameter support (shareable filtered views)
+- Backend filtering (API query parameter `?min_rating=3`)
+- Filter by aggregate rating (average from all users, not just current user)
+- Filter animation transitions (smooth photo grid updates)
+
+**Monitoring & Metrics:**
+- Track filter usage (how often users use filter, which ratings are most filtered)
+- Monitor performance with large albums (1000+ photos)
+- Collect user feedback on filter UX
+
+**Known Limitations:**
+- Filter state not synced across devices (session-only, Pinia store)
+- Page reload clears filter (acceptable per requirements)
+- Cannot filter for exact rating (only minimum threshold)
+- Cannot filter explicitly for unrated photos (they're excluded from filtered results)
+- Depends on Feature 001 (blocking dependency)
+
+---
+
+_Last updated: 2026-01-03_
diff --git a/docs/specs/4-architecture/features/006-photo-rating-filter/spec.md b/docs/specs/4-architecture/features/006-photo-rating-filter/spec.md
new file mode 100644
index 00000000000..d3c14a7391a
--- /dev/null
+++ b/docs/specs/4-architecture/features/006-photo-rating-filter/spec.md
@@ -0,0 +1,367 @@
+# Feature 006 – Photo Star Rating Filter
+
+| Field | Value |
+|-------|-------|
+| Status | Draft |
+| Last updated | 2026-01-03 |
+| Owners | Agent |
+| Linked plan | `docs/specs/4-architecture/features/006-photo-rating-filter/plan.md` |
+| Linked tasks | `docs/specs/4-architecture/features/006-photo-rating-filter/tasks.md` |
+| Roadmap entry | #006 |
+
+> Guardrail: This specification is the single normative source of truth for the feature. Track high- and medium-impact questions in [docs/specs/4-architecture/open-questions.md](docs/specs/4-architecture/open-questions.md), encode resolved answers directly in the Requirements/NFR/Behaviour/UI/Telemetry sections below (no per-feature `## Clarifications` sections), and use ADRs under `docs/specs/5-decisions/` for architecturally significant clarifications (referencing their IDs from the relevant spec sections).
+
+## Overview
+Add a star rating filter control to the photo panel that allows users to quickly filter photos in the current album by minimum star rating threshold. The filter displays as 5 hoverable/clickable stars positioned to the left of the photo layout selection buttons. Clicking a star filters photos to show rating ≥ selected star (e.g., click 3rd star → show 3, 4, 5 star photos). Clicking the same star again removes the filter. The filter is frontend-only (no backend changes), uses client-side filtering on the already-loaded photo array, and only appears when at least one photo in the album has a rating. Filter state persists in the Pinia state store during the session.
+
+## Goals
+- Provide quick visual filtering of photos by minimum star rating threshold
+- Display filter control only when relevant (at least one rated photo exists)
+- Support intuitive interaction: click star to set minimum threshold, click again to clear filter
+- Maintain filter state in Pinia store (similar to NSFW visibility pattern)
+- Implement fully client-side filtering (no API calls)
+- Position filter control to the left of existing photo layout selection buttons
+
+## Non-Goals
+- Backend API changes or query parameter filtering
+- Exact rating matching (filter shows ≥ N stars, not == N stars)
+- Filtering for unrated photos explicitly (unrated excluded from filtered results)
+- Multiple rating selection (checkboxes, multi-select)
+- Persistent filter state across page reloads (localStorage)
+- Filter controls for albums (feature is photo-only)
+- Range sliders or complex UI controls
+
+## Functional Requirements
+
+| ID | Requirement | Success path | Validation path | Failure path | Telemetry & traces | Source |
+|----|-------------|--------------|-----------------|--------------|--------------------|--------|
+| FR-006-01 | Display star filter control only when at least one photo has a rating | Component computes `hasRatedPhotos` from photo array. If true, render 5-star filter control. If false, hide filter control entirely. | N/A (UI conditional rendering) | N/A | None | User requirement (conditional display) |
+| FR-006-02 | Filter photos by minimum star rating threshold (≥ N stars) | User clicks star N (1-5) → filter state updates to N → photo list filtered to show only photos with `user_rating >= N`. Unrated photos (null/0 rating) excluded from results. | N/A (client-side filtering) | N/A | None | User requirement Q-006-01, Q-006-04 |
+| FR-006-03 | Toggle filter off by clicking same star | User clicks star N when filter already set to N → filter state resets to null → all photos shown (no filtering applied). | N/A | N/A | None | User requirement Q-006-01 |
+| FR-006-04 | Persist filter state in Pinia store during session | Filter state stored in PhotosState store (similar to NSFW visibility). State persists during navigation within album but resets on page reload or closing tab. | N/A | N/A | None | User requirement Q-006-03 |
+| FR-006-05 | Apply filtering only when filter is active and rated photos exist | If filter state is null OR no rated photos exist → display all photos unfiltered. If filter state is set AND rated photos exist → apply filter. | N/A | N/A | None | User requirement (conditional filtering) |
+| FR-006-06 | Visual feedback: highlight selected star threshold | Selected star and all stars below it should be visually highlighted (filled) to indicate active filter. Empty stars indicate no filter active. | N/A | N/A | None | UX clarity |
+| FR-006-07 | Position filter control to the left of photo layout selection | Filter control rendered in PhotoThumbPanelControl.vue, positioned before (to the left of) existing layout buttons (squares, justified, masonry, grid). | N/A | N/A | None | User requirement (placement) |
+
+## Non-Functional Requirements
+
+| ID | Requirement | Driver | Measurement | Dependencies | Source |
+|----|-------------|--------|-------------|--------------|--------|
+| NFR-006-01 | Filtering must be instant (no API call, client-side only) | Performance - immediate user feedback | Filter applied synchronously via computed property, no observable delay | Vue 3 reactivity, photo data already loaded | User requirement (frontend-only) |
+| NFR-006-02 | Filter control must be accessible via keyboard | Accessibility - keyboard navigation support | Tab to focus stars, Enter/Space to select, arrow keys to navigate stars, aria-labels present | PrimeVue accessibility features | WCAG 2.1 AA |
+| NFR-006-03 | Component code must follow Vue 3 Composition API and TypeScript conventions | Code quality and maintainability | Follows existing patterns in PhotoThumbPanelControl.vue, TypeScript types for props/state | Vue 3, TypeScript, existing codebase patterns | [docs/specs/3-reference/coding-conventions.md](docs/specs/3-reference/coding-conventions.md) |
+| NFR-006-04 | Filtering performance must handle 1000+ photos smoothly | Performance - large album support | Computed property recalculation completes within 100ms for 1000 photos | Vue 3 computed property optimization | User experience |
+
+## UI / Interaction Mock-ups
+
+### PhotoThumbPanelControl with Star Filter (No Filter Active)
+```
+┌────────────────────────────────────────────────────┐
+│ Photos Panel │
+├────────────────────────────────────────────────────┤
+│ │
+│ [☆][☆][☆][☆][☆] [⊞][≡][⊟][▦] │
+│ ^Star Filter^ ^Layout buttons^ │
+│ │
+│ Photo grid below... │
+└────────────────────────────────────────────────────┘
+
+Legend:
+ [☆] = Empty star (no filter active)
+ Filter only visible if at least one photo has rating
+```
+
+### Star Filter Active (Minimum 3 Stars Selected)
+```
+┌────────────────────────────────────────────────────┐
+│ Photos Panel │
+├────────────────────────────────────────────────────┤
+│ │
+│ [★][★][★][☆][☆] [⊞][≡][⊟][▦] │
+│ ^3+ stars^ ^Layout buttons^ │
+│ │
+│ Showing photos with rating ≥ 3 stars │
+│ (excludes 1-2 star and unrated photos) │
+└────────────────────────────────────────────────────┘
+
+Legend:
+ [★] = Filled star (stars 1-3 filled → filter ≥ 3)
+ [☆] = Empty star (stars 4-5 not part of threshold)
+ Click on star 3 again → clear filter
+```
+
+### Star Filter Hover Interaction
+```
+User hovers over star 4:
+[★][★][★][★*][☆]
+ ^Hover highlight
+
+User clicks star 4:
+[★][★][★][★][☆] → Filter set to ≥ 4 stars
+ Shows 4 and 5 star photos only
+
+User clicks star 4 again:
+[☆][☆][☆][☆][☆] → Filter cleared
+ Shows all photos
+```
+
+### Filter Hidden (No Rated Photos)
+```
+┌────────────────────────────────────────────────────┐
+│ Photos Panel │
+├────────────────────────────────────────────────────┤
+│ │
+│ [⊞][≡][⊟][▦] (Star filter hidden) │
+│ ^Layout buttons^ │
+│ │
+│ All photos shown (none have ratings) │
+└────────────────────────────────────────────────────┘
+
+Legend:
+ Star filter not rendered when no photos have ratings
+```
+
+## Branch & Scenario Matrix
+
+| Scenario ID | Description / Expected outcome |
+|-------------|--------------------------------|
+| S-006-01 | Album has no rated photos → star filter hidden, all photos displayed |
+| S-006-02 | Album has ≥1 rated photo → star filter visible (5 empty stars) |
+| S-006-03 | User clicks star 3 (no filter active) → filter set to ≥3, stars 1-3 filled, photos filtered to show 3+ star ratings |
+| S-006-04 | User clicks star 5 → filter set to ≥5, all 5 stars filled, only 5-star photos shown |
+| S-006-05 | User clicks star 1 → filter set to ≥1, star 1 filled, all rated photos shown (excludes unrated) |
+| S-006-06 | User clicks star 3 when filter already ≥3 → filter cleared, all stars empty, all photos shown |
+| S-006-07 | User clicks star 4, then clicks star 2 → filter changes from ≥4 to ≥2, stars 1-2 filled, photos with 2+ stars shown |
+| S-006-08 | User navigates within album with filter active → filter state persists |
+| S-006-09 | User reloads page → filter state resets to no filter (all photos shown) |
+| S-006-10 | User rates a photo while filter active → filtered list updates reactively if photo meets threshold |
+
+## Test Strategy
+- **Core:** N/A (no backend changes)
+- **Application:** N/A (no backend changes)
+- **REST:** N/A (no API changes)
+- **CLI:** N/A (no CLI changes)
+- **UI (JS/Selenium):**
+ - Unit tests for filter computed property logic (≥ threshold filtering)
+ - Unit tests for hasRatedPhotos detection
+ - Unit tests for toggle behavior (click star → set filter, click again → clear)
+ - Component tests for PhotoThumbPanelControl with filter rendering
+ - Integration tests for filter state persistence in PhotosState store
+ - Visual regression tests for star filter UI (empty, partial filled, all filled)
+ - Accessibility tests for keyboard navigation (Tab, Enter, Arrow keys)
+ - Performance tests with 1000+ photos
+- **Docs/Contracts:** N/A (no API contracts)
+
+## Interface & Contract Catalogue
+
+### Domain Objects
+| ID | Description | Modules |
+|----|-------------|---------|
+| DO-006-01 | Photo data with user_rating field (existing PhotoResource) | UI |
+
+### API Routes / Services
+N/A - No API changes required
+
+### CLI Commands / Flags
+N/A - No CLI changes required
+
+### Telemetry Events
+N/A - No telemetry events (per project scope)
+
+### Fixtures & Sample Data
+| ID | Path | Purpose |
+|----|------|---------|
+| FX-006-01 | resources/js/components/gallery/photoModule/__tests__/fixtures/photos-rating-filter.json | Sample photo data with various ratings (0-5) for filter testing |
+
+### UI States
+| ID | State | Trigger / Expected outcome |
+|----|-------|---------------------------|
+| UI-006-01 | No filter active (empty stars) | Default state or user clears filter. All photos displayed (if rated photos exist, filter is visible). |
+| UI-006-02 | Filter active (≥N stars filled) | User clicks star N. Stars 1-N filled, stars N+1-5 empty. Photos with rating ≥ N shown. |
+| UI-006-03 | Filter hidden (no rated photos) | Album has no photos with ratings. Star filter control not rendered. |
+| UI-006-04 | Star hover state | User hovers over star N. Visual highlight on star N (preview state). |
+| UI-006-05 | Star focused (keyboard nav) | User tabs to star filter. Visual focus outline on current star. |
+
+## Telemetry & Observability
+No telemetry events are defined for this feature per project scope.
+
+## Documentation Deliverables
+- Update [docs/specs/4-architecture/roadmap.md](docs/specs/4-architecture/roadmap.md) with Feature 006 entry
+- Update [docs/specs/4-architecture/knowledge-map.md](docs/specs/4-architecture/knowledge-map.md) with:
+ - PhotoThumbPanelControl.vue modifications (star filter control)
+ - PhotosState.ts modifications (filter state property)
+ - Filtering logic documentation
+
+## Fixtures & Sample Data
+Create fixture file `resources/js/components/gallery/photoModule/__tests__/fixtures/photos-rating-filter.json` with sample photo data including:
+- Photos with ratings 1-5 (at least 2 photos per rating level)
+- Photos with no rating (user_rating: null or 0)
+- Album with no rated photos (all user_rating: null)
+- Album with mixed rated/unrated photos
+
+## Spec DSL
+
+```yaml
+domain_objects:
+ - id: DO-006-01
+ name: Photo (existing PhotoResource)
+ fields:
+ - name: id
+ type: string
+ - name: user_rating
+ type: integer | null
+ constraints: "0-5 or null"
+
+routes: []
+
+cli_commands: []
+
+telemetry_events: []
+
+fixtures:
+ - id: FX-006-01
+ path: resources/js/components/gallery/photoModule/__tests__/fixtures/photos-rating-filter.json
+ purpose: Sample photo data for rating filter testing
+
+ui_states:
+ - id: UI-006-01
+ description: No filter active (empty stars)
+ - id: UI-006-02
+ description: Filter active (≥N stars filled)
+ - id: UI-006-03
+ description: Filter hidden (no rated photos)
+ - id: UI-006-04
+ description: Star hover state
+ - id: UI-006-05
+ description: Star focused (keyboard navigation)
+
+ui_components:
+ - id: UC-006-01
+ name: PhotoThumbPanelControl.vue (modified)
+ location: resources/js/components/gallery/photoModule/PhotoThumbPanelControl.vue
+ modifications: Add star filter control (5 clickable stars) before layout buttons
+ - id: UC-006-02
+ name: PhotosState.ts (modified)
+ location: resources/js/stores/PhotosState.ts
+ modifications: Add photo_rating_filter property (null | 1-5) for filter state
+```
+
+## Appendix
+
+### Resolved Open Questions
+All open questions (Q-006-01, Q-006-02, Q-006-03, Q-006-04) have been resolved and incorporated into the spec:
+
+- **Q-006-01:** Filter UI uses hoverable star list with minimum threshold filtering and toggle-off
+- **Q-006-02:** Unrated photos excluded from filtered results (addressed by minimum threshold logic)
+- **Q-006-03:** Filter state persisted in Pinia store (like NSFW visibility), not localStorage
+- **Q-006-04:** Minimum threshold filtering (≥ N stars) rather than exact match or multi-select
+
+### Implementation Notes
+
+1. **Component Architecture:**
+ - Modify existing `PhotoThumbPanelControl.vue` to add star filter control
+ - Add computed property `hasRatedPhotos` to check if any photo has user_rating > 0
+ - Render star filter conditionally: `v-if="hasRatedPhotos"`
+ - Add computed property `filteredPhotos` to PhotosState or parent component
+
+2. **State Management:**
+ - Add `photo_rating_filter: null | 1 | 2 | 3 | 4 | 5` to PhotosState store
+ - Default value: `null` (no filter active)
+ - Action: `setPhotoRatingFilter(rating: null | 1-5)`
+ - Getter: `photoRatingFilter`
+
+3. **Filtering Logic:**
+ ```typescript
+ const filteredPhotos = computed(() => {
+ const filter = photosStore.photoRatingFilter;
+ const hasRated = photos.value.some(p => p.user_rating > 0);
+
+ // Only apply filter if active AND rated photos exist
+ if (filter === null || !hasRated) {
+ return photos.value;
+ }
+
+ return photos.value.filter(p =>
+ p.user_rating !== null &&
+ p.user_rating >= filter
+ );
+ });
+ ```
+
+4. **Star Control Component:**
+ - 5 clickable star icons (PrimeVue `pi-star` and `pi-star-fill`)
+ - Stars 1-N filled when filter = N
+ - Click star N: if filter !== N → set filter to N, else → set filter to null
+ - Hover effect on stars (preview state)
+ - Aria-labels: "Filter by N stars or higher" for each star
+ - Keyboard support: Tab to focus, Arrow keys to select star, Enter to activate
+
+5. **Styling Considerations:**
+ - Star filter inline with layout buttons: `flex flex-row items-center gap-2`
+ - Stars grouped with small gap: `inline-flex gap-1`
+ - Star size: match layout button icon size (e.g., `text-lg` or `w-5 h-5`)
+ - Filled stars: `text-yellow-500` or `text-primary`
+ - Empty stars: `text-gray-300 dark:text-gray-600`
+ - Hover: `hover:text-yellow-400 cursor-pointer`
+ - Separator between filter and layout buttons: border or margin
+
+6. **Accessibility:**
+ - Star filter wrapped in ``
+ - Each star button: `