- Create bruno/library/browse-folders.yml
- Single manual test case for admin context
- Unit/integration tests cover all scenarios (no auth, user, admin)
- Bruno provides quick manual API contract verification
Fixes: Issue 2 (manual testing)
- Add unit tests in internal/services/library_service_test.go
- Test path traversal protection
- Test non-existent path handling
- Test file vs directory validation
- Test successful directory listing
- Add integration tests in cmd/server/tests/library_browse_test.go
- Use setupTestServer() helper from test_helpers.go
- Test no authentication returns 401
- Test regular user returns 403 forbidden
- Test admin can browse directories
- Test path traversal blocking
- All tests use table-driven approach with t.Run()
Fixes: Issue 2 (tests)
- Add BrowseDirectories() to library service with path traversal protection
- Add BrowseDirectories handler with proper error handling
- Register GET /api/libraries/browse endpoint (admin-only)
- Returns current path, parent path, and list of subdirectories
- Security: blocks "..", validates path exists, checks is directory
Fixes: Issue 2 (backend)
- Add optional data parameter to apiDelete() with generic type safety
- Enables DELETE requests with request bodies (needed for folder deletion)
- 100% backward compatible (optional parameter)
- Supports type-safe request body passing
Part of: Issue 1
- Reuse Create Library modal for edit mode
- Add hidden library-id input to track create vs edit
- Update handleCreateLibrarySubmit to detect mode and use PUT vs POST
- Implement editLibrary() to populate modal with existing data
- Pass library data to Edit button via data attributes
- Reset modal title when opening for create mode
Fixes: Issue 3
Complete refactor of /admin/library page to use server-side rendering
and TypeScript, fixing broken Create Library button and improving UX.
Backend Integration:
- Update template signature to accept libraries and users parameters
- Add SSR rendering of library list (no empty state)
- Add SSR rendering of user select dropdown
- Data fetched in router handler and passed to template
TypeScript Conversion:
- Create web/src/library.ts (394 lines) - complete rewrite of inline JS
- Convert all inline JavaScript to TypeScript
- Fix critical data.data API response bug
- Replace broken HTMX form with fetch() API calls
- Implement event delegation for dynamic button clicks
- Add missing editLibrary() function (placeholder)
- Add proper error handling with toast notifications
Template Changes:
- Remove 229 lines of inline JavaScript
- Update Create Library button: onclick → data-action
- Update form: remove HTMX attributes, add onsubmit handler
- Remove duplicate script tags from <head> section
- Keep all script loading at end of <body> for performance
Bug Fixes:
- Fix API response handling: data → data.data
- Replace broken hx-headers (JavaScript not supported in HTMX)
- Fix modal z-index and visibility classes
Progressive Enhancement:
- Page works without JavaScript (SSR content visible)
- JavaScript enhances with interactive features
- Follows PROJECT_GUIDELINES: procedural style, no OOP
Update the /admin/library route handler to fetch and pass data to
template for server-side rendering, improving page load performance.
Changes:
- Fetch all libraries using ListLibrariesData() helper
- Fetch all users for visibility management
- Convert database rows to template types (LibraryData, User)
- Pass data to AdminLibrary template for SSR
- Follows existing pattern from dashboard and custom-section pages
Benefits:
- Faster initial page load (no AJAX fetch)
- Better UX (content visible immediately)
- Progressive enhancement (works without JS)
Add ListLibrariesData() method to LibraryHandler to support
server-side rendering of all libraries on the /admin/library page.
This follows the existing pattern of GetUserVisibleLibrariesData() and
GetLibraryTypeData() methods, which return data structures instead of
JSON for template rendering.
Changes:
- Add ListLibrariesData() method (3 lines)
- Returns []database.ListLibrariesRow for template consumption
- Called by frontend route handler for SSR
Update admin dashboard sidebar to point to user administration page
instead of profile settings:
- Change link from /profile to /admin/users
- Update label from 'Profile Settings' to 'User Administration'
This completes the admin navigation refactoring to use dedicated
admin pages instead of the main user profile page.
Add --force-recreate flag to all build targets that create containers:
- build: Force recreation when building all containers
- rebuild-app: Force recreation of app container only (fixes template update issue)
- build-force: Force recreation even when using --no-cache
- test-env-up: Force recreation in test environment
This fixes a critical issue where 'make rebuild-app' would build a new image
but continue running the old container, causing template changes to not appear.
Root cause: podman-compose up --build doesn't recreate containers if they're
already running, even when a new image is built. The --force-recreate flag
ensures containers are recreated with the new image.
BuildKit caching still works as expected - cache is used during image build,
while --force-recreate ensures the new image is actually deployed.
Changes:
- build: Add --force-recreate flag
- rebuild-app: Add --force-recreate flag, update success message
- build-force: Add --force-recreate flag
- test-env-up: Add --force-recreate flag
Verified: Template changes now appear immediately after rebuild.
- Add password requirements checklist with visual indicators (✓/○)
- Implement real-time validation for length, case, numbers, special chars
- Add confirm password field with matching validation
- Disable submit button until all requirements are met
- Add TypeScript client-side validation with password manager compatibility
- Replace inline nav code with @Header component in admin.templ
- Replace inline nav code with @Header component in admin_library.templ
- DRY up navigation, theme switcher, and user menu across admin pages
- Optimize Dockerfile layer caching with --mount=type=cache for Go modules and npm
- Reorganize Dockerfile layers for better cache hit rates
- Improve .dockerignore organization with categorized comments
- Add Makefile targets: up, down, rebuild-app, restart, ps
- Enhance Makefile help output with categorized sections
- Add theme.js script for proper theme support
- Update form to use CSS variables (--bg-primary, --text-secondary, etc.)
- Replace hardcoded colors with theming system
- Add optional first_name and last_name fields
- Update button to use .btn-primary class
- All form inputs now properly themed
Vendored the sevenzip package to eliminate dependency chain:
- sevenzip -> go4.org -> 25+ Google/Cloud/telemetry packages
Changes:
- Added internal/sevenzip/ with full package source
- Inlined go4.org/readerutil into multireaderat.go
- Updated all internal imports to use bookhoard/internal/sevenzip
- Preserved .cb7 comic archive support
This reduces bloat by ~4.9 MB and removes unused telemetry
dependencies while maintaining all functionality.
Add explicit check to verify target user exists in database before
attempting deletion. Previously, the handler would return 200 OK when
trying to delete non-existent users.
Changes:
- Add userFound flag to track if target user was found in user list
- Explicitly check pgtype.UUID.Bytes against all users' IDs
- Return 404 Not Found if user doesn't exist (before last admin check)
- Supports both JSON and HTML (HTMX) response formats
This fixes the failing test:
- TestDeleteUserConsolidated/DELETE_/api/auth/profile/:id_-_Delete_non-existent_user
The check uses the existing ListUsers result, so no additional database
query is required. The pgtype.UUID.Bytes comparison ensures exact
16-byte UUID matching.
The test was using simple passwords ('password1', 'password2') that
failed complexity validation before the mismatch check could run.
Changed to use valid complex passwords that don't match:
- new_password: 'NewPassword123!'
- confirm_password: 'DifferentPass123!'
This properly tests the mismatch validation path. All 4 subtests in
TestUpdatePasswordAdminMode now pass.
Add comprehensive testing documentation covering:
- Test email domain usage (@tests.bookhoard.internal)
- Standard test users and their credentials
- Test lifecycle and cleanup process
- How to write tests properly
- Running tests (make targets, specific tests)
- Test organization and helper functions
This helps developers understand the testing infrastructure and
prevents accidental data loss when running tests.
Update test email domain in Bruno API collection files:
- Login User.yml
- Register User.yml
- Update Profile.yml
Ensures API tests use the dedicated test domain and won't conflict
with real user data when developers run tests.
Update email domain in remaining test files:
- device_cap_test.go
- device_test.go
- queue_test.go
- refresh_token_test.go
- seven_day_session_test.go
All test files now consistently use the dedicated test domain
to prevent conflicts with real user data.
- Fix username conflict: use unique name 'updateduser-admin-test'
- Fix 'last admin' test: explicitly delete regular user and verify admin count
- Add missing Content-Type header to PUT request
- Fix assertion: match actual validator error message ('oneof')
- Update email domain references to @tests.bookhoard.internal
All 5 subtests now pass:
- Admin update username ✓
- Admin promote user to admin ✓
- Try to demote last admin ✓
- Non-admin tries update ✓
- Invalid role ✓
- Change test email domain from @example.com to @tests.bookhoard.internal
- This prevents accidental deletion of real user data when self-hosters run tests
- Improve test cleanup: delete ALL users with test domains before each test
- Ensures complete test isolation by cleaning up users from previous tests
- Handles edge cases where tests promote users to admin or modify accounts
The @tests.bookhoard.internal domain is clearly for testing only and
won't conflict with real user emails.
Add collections for Delete User, Reset User Password, and Update User
endpoints. Remove obsolete collections for individual profile update
operations. Update Update Profile collection to reflect new consolidated
API structure.
Add documentation for delete_user and reset_user_password endpoints.
Update update_profile to reflect consolidated endpoint. Remove obsolete
documentation for individual update operations. Add profile guide for
end-users. Update API_CONSOLIDATION_PLAN.md with implementation status.
Add admin_users.templ for user management interface with delete, password
reset, and edit capabilities. Add profile.templ and related components for
user profile management. Remove admin_profile.templ. Update all templates
to use new consolidated API endpoints.
Add DELETE /api/auth/users/:id, PUT /api/auth/users/:id/password, and
PUT /api/auth/users/:id routes. Remove individual profile update routes
in favor of consolidated endpoints.
Implement DeleteUser, ResetUserPassword, and UpdateUserAdmin handlers.
Update collections handler to check soft-deleted users. Update dashboard
service to exclude deleted users from statistics.
Add delete_user, reset_user_password, and update_user endpoints to replace
individual update operations. Update database schema to include deleted_at
column for soft deletion. Add DeleteUser, ResetUserPassword, and
UpdateUserAdmin queries. Update Querier with new methods for user management.
Restructure TestOPDSEndpoints and TestOPDSConversion to follow the Kobo
test pattern. Create all media items at parent level before any subtests
run, avoiding token invalidation when setupDeviceTest is called. Subtests
now use pre-created media IDs and device.AuthToken for authentication.
Add Token and RegularToken fields to TestServerSetup for pre-authenticated
access. Update setupTestServer to create fresh users with valid tokens at
initialization time. Simplify createTestMediaItemID to use setup.Token.
Remove loginTestUser, loginRegularUser, loginAdminUser functions in favor
of setup.Token/setup.RegularToken. Update createTestUserOnce and
getTestUserID/getRegularUserID to be idempotent. Update all test files to
use setup.Token instead of calling login helpers.
- Move header.js script from individual templates to header.templ
- Removes duplicate script tags from bookshelf, collections, progress, etc.
- Fixes indentation in docs.templ
Removed unit tests that couldn't work without a database (nil db would
panic). Added comprehensive integration tests for the PreviewCollection
endpoint covering:
- Authentication (no auth, valid auth)
- Input validation (missing/invalid library ID, invalid JSON)
- Manual book selection
- Rule-based filtering
- Limit parameter handling
- Duplicate and invalid book ID handling
The PreviewCollection endpoint was missing authentication verification,
allowing unauthenticated access to the preview functionality. Added
check for user in context, returning 401 Unauthorized if missing.
The TokenLookup config was missing the Bearer prefix stripper, causing
all authenticated requests to fail with 'token is malformed'. The JWT
library was trying to decode 'Bearer eyJh...' as a token, failing at
the space character.
Changed from: 'cookie:token,header:Authorization'
Changed to: 'cookie:token,header:Authorization:Bearer '
This fixes all integration tests that use Bearer token authentication.
- Add renderErrorPage helper for consistent error rendering
- Add ensureUserExistsMiddleware to detect deleted users and redirect to login
- Add catch-all 404 handler for unknown routes
- Gracefully handle data loading failures with error messages instead of crashing
- Log errors for debugging while still rendering pages
Add standalone error page template for graceful error display when the
main app fails. Includes inline CSS since error pages must work when
CSS fails to load. Add error toast component for displaying errors.
Phase 12 - Bruno API Tests Standardization
Convert all custom section builder tests to .yml format:
- preview-collection.bru → preview-collection.yml
- create-custom-section-manual.bru → create-custom-section-manual.yml
- create-custom-section-missing-fields.bru → create-custom-section-missing-fields.yml
- create-custom-section-rules.bru → create-custom-section-rules.yml
All Bruno tests now consistently use .yml extension across the project.
Tests cover:
- Preview collection with filter rules
- Create collection with manual book selection
- Validation of required fields
- Multiple filter rules with operators
Part of Carousel Dashboard Plan completion
Phase 11 - Unit and Integration Tests
Service Layer Tests (dashboard_service_test.go):
- Test filterHiddenCollections with multiple scenarios
- Test reorderCollections with custom orders
- Test sortByPriority sorting logic
- All 6 tests passing
Handler Tests (dashboard_test.go):
- Test BuildSections type conversion
- Test textToString helper function
- Test getViewAllURL mapping
- All 7 tests passing
Preview Tests (collections_preview_test.go):
- Test preview endpoint validation
- Test limit validation
- Test rule validation
- 6 test scenarios
Integration Tests (dashboard_integration_test.go):
- Test GET /api/dashboard/sections end-to-end
- Test PUT /api/dashboard/preferences
- Test POST /api/dashboard/restore-system-collection
- Test authentication and validation
- 9 test scenarios total
Part of Carousel Dashboard Plan completion
Phase 13 - Documentation Updates
- Add complete API documentation for custom section builder
- Document all 14 filter fields with operators
- Include example use cases (Sci-Fi Favorites, High Rated, etc.)
- Document preview endpoint and collection creation
- Verify existing dashboard.md documentation is comprehensive
Part of Carousel Dashboard Plan completion
Phase 10.5.1: Add /custom-section frontend route
- Added route handler in internal/router/frontend.go
- Fetches user libraries and renders custom section builder template
Phase 10.5.2: Create custom section builder template
- Created templates/custom_section.templ with full UI
- Includes section details form, filter rules builder, manual book selection
- Live preview functionality with preview container
- Form actions for save/cancel
Phase 10.5.3: Create custom-section-builder TypeScript
- Created web/src/custom-section-builder.ts with 13+ filter fields
- Filter fields: title, author, genre, series, progress, rating, date_added, last_read, publisher, language, format, tags, narrators
- Procedural/imperative style (no OOP) as per guidelines
- Rule builder with AND/OR logic support
- Book search and multi-select functionality
- Live preview via /api/collections/preview endpoint
- Form validation and submission to /api/collections
Phase 10.5.4: Build TypeScript modules
- Compiled custom-section-builder.ts to web/static/custom-section-builder.js
- Verified successful compilation with no errors
- All existing TypeScript modules continue to compile
Phase 10.5.5: Add Bruno tests for custom section creation
- create-custom-section-rules.bru: Test creating section with filter rules
- create-custom-section-manual.bru: Test creating section with manual book selection
- create-custom-section-missing-fields.bru: Test error handling for missing required fields
Phase 10.6: Build Verification
- ✅ TypeScript modules compile successfully
- ✅ Templates generate successfully
- ✅ Go build succeeds with no compilation errors
- ✅ All build artifacts verified (dashboard.js, custom-section-builder.js, dashboard_templ.go, custom_section_templ.go)
This completes the Custom Section Builder feature, allowing users to create
personalized dashboard sections with flexible filter rules or manual book selection.
Add preview endpoint for custom section builder and rule evaluation:
Handler Implementation (internal/handlers/collections.go):
- PreviewCollection method: Evaluates filter rules and returns matching items without saving
* Accepts library_id, rules array, manual_book_ids array, and limit
* Evaluates rules against all library items using collectionService.EvaluateRules
* Adds manually selected books to results
* Deduplicates manual books (avoids adding same book twice)
* Applies limit (default: 20, max: 100)
* Returns array of BookInfo with matching items
- Helper function: mediaItemsToListMediaItemsRow
* Converts database.MediaItems to database.ListMediaItemsRow
* Required for EvaluateRules which expects ListMediaItemsRow type
Route Registration (internal/router/collections.go):
- POST /api/collections/preview
- Protected by JWT middleware
- Part of collections API group
Why This Endpoint is Necessary:
- Allows users to see what books match their filter rules BEFORE saving
- Avoids creating incorrect collections
- Enables testing different rule combinations quickly
- Reuses existing service logic (collectionService.EvaluateRules)
- Client-side preview would require downloading entire library (10,000+ books)
- Would duplicate 500+ lines of rule evaluation logic in TypeScript
- Would create maintenance nightmare keeping Go and TypeScript in sync
Bruno Test (bruno/collections/preview-collection.bru):
- Tests POST /api/collections/preview endpoint
- Validates status 200 response
- Validates items array in response
- Example request with genre filter rule
This endpoint is required for both the web UI Custom Section Builder and future mobile apps.
Replace library browser with Carousel-style collections carousel:
Template Changes (templates/dashboard.templ):
Complete rewrite from library browser to collections carousel:
1. Dashboard Main Template:
- Sticky library selector dropdown
- Customize dashboard button (settings modal)
- Refresh button
- Loading spinner for async operations
- Collections container with carousels
2. CollectionCarousel Component:
- Collection header with icon, title, description
- View All link for system collections
- Horizontal scrollable carousel track
- Left/right navigation buttons
- Book cards with cover images
- Empty state handling
3. BookCard Component:
- Aspect ratio [2/3] book cover
- Cover image with fallback to placeholder
- Title and author display
- Click handler for viewing book details
- Hover scale animation
4. DashboardSettingsModal Component:
- Draggable collection list for reordering
- Toggle switches for collection visibility
- "System" badges for system collections
- "Restore" buttons for system collections
- Items per section slider (10-50, step 5)
- Save/Cancel buttons
Template Features:
- Uses IsSystem boolean instead of Type string
- data-is-system attribute for JavaScript
- data-collection-id for DOM manipulation
- Supports drag-and-drop reordering
- Settings modal with live preview
TypeScript Implementation (web/src/dashboard.ts):
Core Functions:
- scrollCarousel: Smooth horizontal scrolling
- openDashboardSettings/closeDashboardSettings: Modal control
- toggleCollectionVisibility: Toggle visibility switches
- saveDashboardSettings: Save preferences to API
* Collects hidden_collections and collection_order
* Calls PUT /api/dashboard/preferences
* Reloads page on success
- restoreSystemCollection: Reset system collection to defaults
* Confirmation dialog
* Calls POST /api/dashboard/restore-system-collection
* Shows toast notifications
- switchLibrary: Switch between libraries
* Async fetch from API
* Re-renders collections
- renderCollections: Client-side rendering of collections
- renderBookCard: Generate book card HTML
- viewBook: Placeholder for book detail view
- reloadPage: Refresh page
- updateItemsCount: Update slider display
- initDragAndDrop: Drag-and-drop event handlers
Event Handling:
- Event delegation for performance
- data-action attributes for handler routing
- Proper type checking and null safety
- Error handling with toast notifications
Type Safety:
- Uses SectionData and BookInfo from api.d.ts
- Proper TypeScript types throughout
- Null checks for DOM elements
- Type assertions where needed
This implements Phase 9: Dashboard Template with unified collections terminology and full TypeScript interactivity.
Update /dashboard route in frontend.go to use unified collections architecture:
Route Changes:
- Use DashboardService to fetch user dashboard preferences
- Get all dashboard sections (system + user collections)
- Pass sections and library data to template
- Support library_id query parameter for library switching
- Default to first visible library if no library_id specified
Service Integration:
- cfg.DashboardService.GetDashboardPreferences: Fetch user preferences
* hidden_collections: Collections to hide from dashboard
* collection_order: Custom collection ordering
* items_per_section: Number of items per collection
- cfg.DashboardService.GetDashboardSections: Fetch all sections
* System collections (user_id = NULL): continue-reading, recently-added, recently-read, not-started
* User collections: User-created collections marked for dashboard
* Applies user preferences: filters hidden, reorders, sorts by priority
- handlers.BuildSections: Convert service types to handler types
Data Flow:
1. Get user template data with theme
2. Get library_id from query param or default to first library
3. Fetch user dashboard preferences
4. Fetch dashboard sections with preferences applied
5. Convert to handler types for template rendering
6. Render template with sections and library data
Template Signature Change:
- OLD: templates.Dashboard(user)
- NEW: templates.Dashboard(user, sections, libData, currentLibraryID)
This implements Phase 8: SSR Template Routes with unified collections architecture.
Add DashboardService and DashboardHandler to application configuration:
Router Config Updates (internal/router/router.go):
- Add services import for DashboardService type
- Add DashboardService field to Config struct
- DashboardService: Used by SSR routes in frontend.go for data fetching
- DashboardHandler: Used by API routes in dashboard.go for JSON endpoints
Server Initialization (cmd/server/main.go):
- Create dashboardService instance using services.NewDashboardService(queries)
- Keep dashboardHandler creation (already exists from Phase 4)
- Add DashboardService to routerConfig
- Both services now available for dependency injection
Test Helpers (cmd/server/tests/test_helpers.go):
- Create dashboardService instance for testing
- Create dashboardHandler instance for testing
- Add both DashboardService and DashboardHandler to routerConfig
- Ensures test environment matches production setup
Architecture Rationale:
- DashboardService: Service layer with business logic (reusable by SSR, mobile)
- DashboardHandler: HTTP handler layer (JSON API endpoints)
- Separation allows SSR templates to call service directly
- API routes use handler for proper HTTP response handling
- Mobile apps can use API endpoints via DashboardHandler
All three files updated consistently for complete integration.
Update and create Bruno API tests to reflect new unified collections architecture:
Updated Tests:
1. get-dashboard-sections.yml
- Updated response structure documentation
- Changed from type field to is_system boolean
- Changed from id to media_item_id in items
- Added priority field documentation
- Updated example response to show unified collections structure
- Added test for sections array in response
2. update-preferences.yml
- Changed HTTP method from POST to PUT (matching handler implementation)
- Updated request body field names:
* hidden_sections → hidden_collections
* section_order → collection_order
- Updated documentation with new field names
- Updated example request with valid system collection names
New Tests:
3. restore-system-collection.yml
- Tests POST /api/dashboard/restore-system-collection endpoint
- Validates collection_name against allowed system collections
- Tests success response with message
- Documents valid collection names:
* continue-reading
* recently-added
* recently-read
* not-started
Test Coverage:
- GET /api/dashboard/sections: Returns all dashboard sections
- PUT /api/dashboard/preferences: Updates user preferences
- POST /api/dashboard/restore-system-collection: Resets system collection
All tests follow Bruno YAML format with proper authentication via auth: inherit.
Create dashboardHandler instance and add to router config:
- Initialize dashboardHandler using handlers.NewDashboardHandler(queries)
- Add dashboardHandler to router.Config for route registration
- All dashboard routes are now available at /api/dashboard/*
Add dashboard route registration and wire up handler:
Router Changes:
- Add DashboardHandler to router.Config struct
- Create internal/router/dashboard.go with dashboard route registration
- Register dashboard routes in main RegisterRoutes function
Dashboard Routes (all protected by JWT):
- GET /api/dashboard/sections: Get dashboard sections for user
* Query params: library_id (required), limit (optional, default 20, max 100)
* Returns: JSON with sections array
- PUT /api/dashboard/preferences: Update dashboard preferences
* Body: library_id, hidden_collections, collection_order, items_per_section
* Returns: Updated preferences
- POST /api/dashboard/restore-system-collection: Restore system collection to defaults
* Body: collection_name (must be valid system collection)
* Returns: Success message
Server Integration:
- Create dashboardHandler in cmd/server/main.go
- Add dashboardHandler to routerConfig
- Routes are automatically registered on server startup
Add dashboard API endpoints with handler layer:
Step 1: Add SectionData to collections.go
- SectionData struct represents dashboard section (carousel of books)
- Used by: Dashboard handler, Templates (SSR), API JSON responses
- Shared type from collections.go (no duplicate definitions)
- Fields: ID, IsSystem, Title, Description, Icon, Items, ViewAllURL, Priority
Step 2: Create dashboard.go handler
- DashboardHandler struct with injected database and dashboard service
- GetSections: Returns dashboard sections as JSON (mobile apps, web UI TypeScript, plugins)
* Validates library_id parameter
* Fetches user dashboard preferences
* Configurable limit (default 20, max 100)
* Calls service layer for business logic
* Converts service types to handler types for JSON serialization
- UpdatePreferences: Saves dashboard preferences
* Validates library_id
* Upserts user dashboard preferences
- RestoreSystemCollection: Resets system collection to defaults
* Validates collection_name against allowed system collections
* Deletes user's copy (system collection reappears automatically)
- BuildSections: Converts service DashboardSection to handler SectionData
* Converts database.MediaItems to handlers.BookInfo
* Uses shared types from collections.go
- getViewAllURL: Maps system collections to their view-all URLs
- Reuses existing textToString helper from collections.go
Architecture Compliance:
- Generic API handler for reuse by SSR, mobile, plugins
- Uses shared types from collections.go (SectionData, BookInfo)
- IsSystem bool matches database field (no string conversion)
- Single service method returns structured data (simpler, less bugs)
- Handler just converts types (no matching logic needed)
- Reusable by mobile apps, web UI, plugins
Create DashboardService with business logic for Carousel-style dashboard:
Service Methods:
- NewDashboardService: Create service instance with injected dependencies
- GetDashboardSections: Fetch all collections (system + user) with their items
* Gets system collections (user_id = NULL) by query type
* Gets user collections with manual + auto-assigned items
* Filters hidden collections based on user preferences
* Reorders collections based on user custom order
* Sorts by priority if no custom order exists
- GetDashboardPreferences: Fetch user dashboard preferences for library
- UpsertDashboardPreferences: Save or update user dashboard preferences
- RestoreSystemCollection: Reset user's copy of system collection to defaults
Helper Methods:
- filterHiddenCollections: Remove hidden collections from results
- reorderCollections: Reorder sections based on user preference
- sortByPriority: Sort sections by priority (lower numbers first)
- getCollectionItemsByQueryType: Return items for system collections by query type
- getUserCollectionItems: Return items for user collections (manual + auto-assign)
Type Conversion Helpers:
- mediaItemsToListMediaItemsRow: Convert MediaItems to ListMediaItemsRow for rule evaluation
- getCollectionItemsRowToMediaItems: Convert GetCollectionItemsForDashboardRow to MediaItems
Architecture Compliance:
- Service layer holds all business logic (reusable by SSR, API, mobile)
- Returns database types (type safety at DB layer)
- Handler converts to API types (clean JSON contracts)
- Uses existing database queries and collection service
- Procedural/imperative style (no OOP)
- Follows existing pattern from collections.go
Add SQL queries for dashboard functionality and system collections:
Dashboard Preferences Queries:
- GetDashboardPreferences: Fetch user preferences for a library
- UpsertDashboardPreferences: Create or update user dashboard preferences
- UpdateDashboardPreferences: Update existing preferences
Dashboard Collections Queries:
- GetSystemCollectionsForDashboard: Fetch system collections (user_id IS NULL)
- GetUserCollectionsForDashboard: Fetch user collections marked for dashboard
- DeleteUserSystemCollection: Delete user's copy of a system collection
System Collection Smart Queries:
- GetContinueReadingItems: Books with 0 < progress < 1
- GetRecentlyAddedItems: Newly added items to library
- GetRecentlyReadItems: Books with progress >= 1
- GetNotStartedItems: Books with progress = 0 or no record
Collection Management Queries:
- GetCollectionItemsForDashboard: Fetch collection items with excluded flag
- GetLibraryItems: Fetch all items in a library
These queries support the unified collections architecture where system
defaults and user-created sections are both collections with user_id
NULL for system-owned and NOT NULL for user-created.
Add support for Carousel-style dashboard with unified collections architecture:
Database Schema Changes:
- Add user_dashboard_preferences table:
- hidden_collections: TEXT[] for managing section visibility
- collection_order: TEXT[] for custom ordering
- items_per_section: INT for limiting items per section
- Update collections table:
- user_id: Make nullable to support system-owned collections (NULL = system)
- show_on_dashboard: BOOLEAN for controlling visibility
- query_type: TEXT for different query types (continue-reading, recently-added, etc.)
- priority: INT for display order (lower = higher priority)
- is_system_collection: BOOLEAN for flagging system defaults
- Update collection_items table:
- Add excluded BOOLEAN for user overrides of auto-assigned items
Indexes:
- idx_collections_dashboard: (user_id, show_on_dashboard, priority) WHERE show_on_dashboard = true
- idx_dashboard_prefs_user_library: (user_id, library_id)
- idx_collection_items_excluded: (collection_id, excluded) WHERE excluded = true
System Collections (pre-seeded defaults):
- continue-reading: Books with 0 < progress < 1
- recently-added: Newly added items to library
- recently-read: Books with progress >= 1
- not-started: Books with progress = 0 or no record
This implements Phase 1 of the Carousel-style dashboard redesign plan.
Update the verification checklist to cover all aspects of Phase 4.6
(CreateCollection manual books support).
New verification sections:
- Section 6.4: Verify CreateCollection Endpoint Manual Books Support
- Struct field verification (ManualBookIDs)
- Validation tag verification (validate:"max=50")
- Handler implementation verification
- Error handling and logging verification
- Graceful degradation verification
- Section 19.1b: Verify Collections Bruno Tests Created
- New bruno/collections/ directory structure
- All 5 required test files
- Test coverage verification
- Section 19.1b: Verify Collections API Documentation
- manual_book_ids field documentation
- Validation limits (max 50)
- Example requests
- Error handling explanation
- Backward compatibility notes
Bug fix:
- Fixed BuildSections function signature to match actual service
(services.DashboardSection instead of services.SectionItems)
All verification includes:
- Step-by-step verification commands
- Common pitfalls to avoid
- Success criteria for each section
Add comprehensive documentation for Phase 4.6 which enables the
CreateCollection endpoint to support manual book selection alongside
auto-assign rules. This is required for the Custom Section Builder.
Key additions:
- Phase 4.6: Update CreateCollection Endpoint (30-45 min)
- Add ManualBookIDs field to CreateCollectionRequest struct
- Implement graceful handling of invalid book IDs
- Add validation (max 50 book IDs) to prevent DoS
- Reuse existing AddBookToCollection service method
- Maintain backward compatibility (field is optional)
- Updated Phase 12.5: Collections Bruno tests
- create-collection-with-manual-books.bru
- create-collection-too-many-books.bru (validation test)
- create-collection-invalid-book-id.bru
- create-collection-rules-only.bru
- create-collection-unauthorized.bru
- Added section 13.3: Collections API documentation
- manual_book_ids field documentation
- Validation limits (max 50 items)
- Example combining auto-assign + manual books
- Error handling explanation
Design decisions:
- Graceful degradation: Collection created even if some books fail
- Reuse existing infrastructure: No new service methods needed
- Backward compatible: Optional field doesn't break existing clients
- UI constraint: 50 book limit prevents abuse while allowing flexibility
Updated Carousel Dashboard documentation to reflect finalized architecture decisions
and resolve discrepancies between plan and verification checklist.
## CAROUSEL_DASHBOARD_PLAN.md Changes
### Added Phase 4.5: Collections Preview Endpoint
- Documented why preview endpoint is required (web UI + mobile apps)
- Explained why client-side preview is a bad idea (download entire library,
code duplication, maintenance nightmare)
- Added full PreviewCollection handler implementation
- Added Bruno test specification
### Enhanced Phase 7: Router Registration & Config Setup
- Renamed from "Router Registration" to "Router Registration & Config Setup"
- Added Step 1: Update router.go Config struct with line numbers
- Added Step 2: Update main.go initialization with line numbers
- Added Step 3: Update test_helpers.go with line numbers
- Added explanation: Why both DashboardService AND DashboardHandler?
### Updated Phase 10.5.4: Collections Preview Endpoint
- Referenced Phase 4.5 (endpoint already implemented earlier)
- Clarified needed for web UI AND mobile apps
- Noted no additional work needed
### Added Phase 10.6: Implementation Checklist
- 30+ checklist items with file paths and verification commands
- Organized by layer (Database, Service, Handler, Router, Templates, TypeScript, Tests, Docs)
- Added Build & Verification section
- Added Timeline Estimate (20-26 hours)
- Added Post-Implementation Tasks
## CAROUSEL_DASHBOARD_VERIFICATION_CHECKLIST.md Changes
### Added Clarification Section (at top)
- Explained all discrepancies between plan and checklist
- Preview endpoint IS in plan (Phase 4.5)
- Custom Section Builder IS in plan (Phase 10.5.2 and 10.5.3)
- Service method names - Plan is correct
- Config struct - Documented with exact line numbers
- DashboardService vs DashboardHandler - Explained why both needed
### Updated Service Method Names (Section 3.2)
Changed to match plan's actual implementation:
- GetDashboardSections (not GetSectionItems)
- filterHiddenCollections (not filterHiddenSections)
- reorderCollections (not reorderSections)
- sortByPriority (new method)
- getUserCollectionItems (not getCollectionSections)
- getCollectionItemsByQueryType (renamed)
### Enhanced Config Verification (Section 6.4)
Added exact line numbers for all 3 files:
- internal/router/router.go lines 58-59
- cmd/server/main.go lines 123-124, 172-173
- cmd/server/tests/test_helpers.go lines 419-420, 458-459
### Updated Preview Endpoint Section (Section 6.3)
Added clear explanation of why endpoint is REQUIRED and why NOT client-side.
### Clarified Custom Section Builder (Sections 8.5, 9.4)
Both now explicitly state "IS in the plan (Phase 10.5)"
## docs/developer/api/dashboard.md Changes
Updated API documentation to match new unified collections architecture:
- Terminology: "smart sections" → "system collections"
- Field: `type: string` → `is_system: boolean`
- Field: `id` → `media_item_id` for books
- Request: `hidden_sections` → `hidden_collections`
- Request: `section_order` → `collection_order`
- Removed: "in-progress" and "unread" smart sections
- Added: Update Dashboard Preferences endpoint
- Added: Restore System Collection endpoint
- Updated: Example responses with new field names and types
- Updated: Error responses table
## Impact
These changes clarify:
1. Preview endpoint is required for both web UI custom section builder and mobile apps
2. Custom Section Builder IS a major feature in the plan (not missing)
3. Service method names use "collections" terminology consistently
4. Config struct updates are clearly documented with exact line numbers (3 files only)
5. Why both DashboardService AND DashboardHandler are needed in Config
All documentation now accurately reflects the finalized Carousel Dashboard architecture.
UPDATES:
- Remove smart_section_types table references
- Update for collections table with user_id, query_type, priority, is_system_collection
- Update TypeScript type examples (8 fields instead of 11)
- Update type field values ('system'/'user' instead of 'smart'/'collection')
- Update method names: getContinueReading, getNotStarted, RestoreSystemCollection
- Update field names: hidden_collections, collection_order
- Update template verification for collection terminology
- Add per-collection restore button verification
- Remove getInProgress and getUnread method references
- Update all example code to match unified architecture
VERIFICATION:
- All checklist items now verify unified collections approach
- Type examples show correct 8-field structure
- System collections properly distinguished from user collections
- Per-collection restore functionality included
BREAKING CHANGES:
- Remove smart_section_types table entirely
- Use collections table for both system defaults and user sections
- Add user_id (nullable), query_type, priority, is_system_collection to collections
- Pre-seed 4 system collections (user_id = NULL): continue-reading, recently-added, recently-read, not-started
FEATURES:
- System collections are now editable by users
- Per-collection restore functionality (restore-system-collection endpoint)
- Single query type for all dashboard items (unified approach)
UPDATES:
- Database schema changes for collections table
- Service layer methods updated (RestoreSystemCollection instead of RestoreSystemCollections)
- API handler with per-collection restore endpoint
- Templates updated with collection terminology
- TypeScript types updated (8 fields instead of 11, type values: 'system'/'user')
- Field names updated: hidden_collections, collection_order
- All tests updated for new architecture
BENEFITS:
- Simpler data model (single table, single concept)
- System defaults use same code path as user collections
- Users can customize system collections
- Easy reset with per-collection restore buttons
Major updates:
- Reduce smart sections from 5 to 4 (removed 'In Progress')
- Continue Reading: 0% < progress < 100%
- Recently Added: newest items
- Recently Read: progress >= 100%
- Not Started: progress = 0% or no record
- Update paths from web/ts/ to web/src/ structure
- Add handler types instead of duplicate template types
- Types defined in internal/handlers/dashboard.go
- Templates import handlers.SectionData, handlers.BookInfo directly
New features:
- Drag-and-drop section reordering
- Section visibility toggles
- Items per section slider
- Manual progress marking (mark as read/unread)
TypeScript updates:
- Use (window as any).api from web/src/api.ts
- Use (window as any).showToast from web/src/toast.ts
- Import types from web/src/types/dashboard.d.ts
- Event delegation via data-action attributes
Add verification checklist for comprehensive plan review:
- Type definition verification against actual API responses
- API contract and endpoint verification
- Cross-reference verification for template-handler types
- Progressive enhancement testing
- Build and deployment verification
- Compiled from web/src/search.ts
- Added proper type annotations
- Fixed null checks for DOM elements
- Added escapeHtml for library name display
- Updated onclick to use window.selectLibraryAndBook
Fixed integration tests to use existing helpers from test_helpers.go:
Changes:
- Replace getUserUUIDFromToken() with getTestUserID(t, db) helper ✅
- Replace parseUUID() with uuid.MustParse() ✅
- Add explicit comments about automatic cleanup via t.Cleanup() ✅
Test Helpers Used (all from test_helpers.go):
- setupTestServer(t) - creates test server with automatic cleanup
- loginTestUser(t, ts, db) - logs in admin user
- loginRegularUser(t, ts, db) - logs in regular user
- setupDeviceTest(t) - creates server + user + device + library
- getTestUserID(t, db) - gets/creates admin test user UUID
- uuid.MustParse() - parses UUID strings
Cleanup Pattern:
- Automatic via t.Cleanup() inside setupTestServer()
- Registered automatically when setupTestServer() is called
- No manual defer setup.Close() needed
- Runs even if test fails or panics
- Cleanup order: queue → connections → server → database
Dashboard-Specific Helper:
- updateDashboardPreferences() - only for dashboard testing
- Saves dashboard preferences for test scenarios
Benefits:
- Uses proven, existing helpers (no reinventing the wheel)
- Automatic cleanup prevents resource leaks
- Follows project testing patterns exactly
- Less custom code = fewer bugs
Added comprehensive documentation of available test helpers:
Available Helpers (from test_helpers.go):
- setupTestServer(t) - Creates test server with auto cleanup via t.Cleanup()
- loginTestUser(t, ts, db) - Logs in admin user, returns JWT token
- loginRegularUser(t, ts, db) - Logs in regular user, returns JWT token
- setupDeviceTest(t) - Creates server + user + device + library
- getTestUserID(t, db) - Gets/creates admin test user UUID
- getRegularUserID(t, db) - Gets/creates regular test user UUID
TestServerSetup Structure:
- Server *httptest.Server
- DB *database.Queries
- DBPool *pgxpool.Pool
- Config *config.Config
- ConnManager, QueueProcessor
- Auto cleanup via t.Cleanup()
Cleanup Pattern:
- Automatic cleanup registered in setupTestServer()
- Runs even if test fails or panics
- Order: queue processor → connection manager → HTTP server → database pool
- No manual defer setup.Close() needed
Updated Integration Tests:
- Added proper imports (database, uuid, pgtype)
- Documented available helpers
- Removed custom helpers that don't exist
- Uses existing project patterns
This ensures developers know what helpers are available and how to use them correctly.
Major architectural improvements:
1. Add generic /api/dashboard/sections JSON endpoint
- Created internal/handlers/dashboard.go (new file)
- Created internal/router/dashboard.go (new file)
- Single source of truth for web UI, mobile apps, plugins
- Follows existing handler/router pattern
2. Update DashboardService to apply user preferences
- GetSectionItems() now accepts sectionOrder and hiddenSections
- filterHiddenSections() removes user's hidden sections
- reorderSections() applies user's custom order
- Ensures consistent behavior across all clients
3. Separate concerns properly
- API handlers in internal/handlers/dashboard.go
- SSR routes remain in internal/router/frontend.go
- Both use same DashboardService (single source of truth)
4. Reorganize implementation phases
- Phase 1-3: Database, service, queries
- Phase 4-6: Handler, router, frontend routes
- Phase 7-9: Templates and settings
- Phase 10-11: TypeScript modules
- Phase 12-13: Documentation and testing
5. Add documentation
- docs/developer/api/dashboard.md (API reference)
- docs/user/dashboard.md (user guide)
6. Bruno tests already exist
- bruno/dashboard/ has 5 comprehensive test files
- Three-context testing (no user, user, admin)
- No additional tests needed
Benefits:
- Uniform dashboard across web, mobile, plugins
- Single source of truth (no duplicate logic)
- User preferences respected by all clients
- Follows established project patterns
- Comprehensive test coverage
Timeline: Updated to reflect 13 phases (19-25 days total with TypeScript)
- Add prerequisites section (TypeScript conversion must be completed first)
- Update execution order: TypeScript (16-21 days) then dashboard (3-4 days)
- Revise Phase 7 TypeScript implementation:
- Change file locations from web/src/ to web/ts/features/dashboard/
- Replace inline onclick with data-action attributes
- Use shared apiClient instead of raw fetch()
- Add event delegation with on() utility
- Import showToast from core instead of global
- Add type definitions matching Go handlers
- Update templates to load new TypeScript module paths
- Add summary of key changes from original plan
- Ensure consistency with TypeScript Conversion Plan patterns
- Create comprehensive plan to convert ~5,500 lines of inline JS to TypeScript
- Hybrid SSR + TypeScript CRUD approach (keeps existing JSON API)
- Event delegation pattern (no inline onclick handlers)
- Shared infrastructure: apiClient, toast, event utilities
- Procedural/imperative style (no OOP, classes, inheritance)
- 6 phases, 16-21 day timeline
- Preserves single API for all clients (web, mobile, plugins)
- No new backend routes needed
Major restructuring of the dashboard implementation plan to better match project guidelines:
- Change from handler types to template types (SectionData, BookCardData)
- Update from TypeScript to inline JavaScript matching existing pattern
- Change from separate handlers to inline routes in frontend.go
- Refactor service to return raw data (handler formats for templates)
- Add Settings template implementation
- Update TypeScript files to use IIFE pattern with window exports
- Change from .bru files to OpenCollection YAML .yml files
- Add Bruno tests note indicating tests already exist in bruno/dashboard/
This aligns the plan with actual project patterns and reduces architectural divergence.
Add after-response scripts to automatically save access and refresh tokens to Bruno environment variables after successful authentication. This eliminates manual token copying during testing.
Changes:
- Refresh Token.yml: Add script to save tokens from refresh response
- Register User.yml: Add script to save tokens from registration response
- Move all files from bruno-yaml/* to bruno/*
- Maintains existing directory structure within categories
- Updates bruno/user/auth files with OAuth2 refresh token flow
- Updates bruno/user/profile files for user profile management
- Adds bruno/dashboard/ directory with dashboard API tests
- Preserves all existing test scenarios and OpenCollection YAML format
- No functional changes - file reorganization only