docs(dashboard): update verification checklist for unified collections 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
This commit is contained in:
2026-02-19 11:42:19 -05:00
parent 1069c82e81
commit 5bb28e5dfa
+108 -121
View File
@@ -24,18 +24,15 @@ type SectionData struct { ... } // DON'T DO THIS - duplicates handlers.SectionD
interface SectionData { interface SectionData {
id: string; // matches Go's json:"id" id: string; // matches Go's json:"id"
type: string; // matches Go's json:"type" type: string; // matches Go's json:"type" ("system" or "user")
title: string; // matches Go's json:"title" title: string; // matches Go's json:"title"
description: string; // matches Go's json:"description" description: string; // matches Go's json:"description"
icon: string; // matches Go's json:"icon" icon: string; // matches Go's json:"icon"
items: BookInfo[]; // matches Go's json:"items" items: BookInfo[]; // matches Go's json:"items"
view_all_url: string; // matches Go's json:"view_all_url" view_all_url: string; // matches Go's json:"view_all_url"
priority: number; // matches Go's json:"priority" priority: number; // matches Go's json:"priority"
is_hidden: boolean; // matches Go's json:"is_hidden"
created_at: string; // matches Go's json:"created_at"
updated_at: string; // matches Go's json:"updated_at"
} }
// All 11 fields from Go struct included - COMPLETE TYPE MATCHING // All 8 fields from Go struct included - COMPLETE TYPE MATCHING
``` ```
### ❌ UNACCEPTABLE: Partial TypeScript Types ### ❌ UNACCEPTABLE: Partial TypeScript Types
@@ -46,7 +43,7 @@ interface SectionData {
type: string; type: string;
title: string; title: string;
items: BookInfo[]; items: BookInfo[];
// Missing: description, icon, view_all_url, priority, is_hidden, created_at, updated_at // Missing: description, icon, view_all_url, priority
// This is a PARTIAL type and violates type safety guidelines // This is a PARTIAL type and violates type safety guidelines
} }
``` ```
@@ -167,20 +164,33 @@ dropdb test_bookhoard
- [ ] `id UUID PRIMARY KEY DEFAULT gen_random_uuid()` - [ ] `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`
- [ ] `user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE` - [ ] `user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE`
- [ ] `library_id UUID REFERENCES libraries(id) ON DELETE CASCADE` - [ ] `library_id UUID REFERENCES libraries(id) ON DELETE CASCADE`
- [ ] `hidden_sections TEXT[] DEFAULT '{}'` - [ ] `hidden_collections TEXT[] DEFAULT '{}'`
- [ ] `section_order TEXT[] DEFAULT '{}'` - [ ] `collection_order TEXT[] DEFAULT '{}'`
- [ ] `items_per_section INT DEFAULT 20` - [ ] `items_per_section INT DEFAULT 20`
- [ ] `created_at TIMESTAMP DEFAULT NOW()` - [ ] `created_at TIMESTAMP DEFAULT NOW()`
- [ ] `updated_at TIMESTAMP DEFAULT NOW()` - [ ] `updated_at TIMESTAMP DEFAULT NOW()`
- [ ] Unique constraint on `(user_id, library_id)` - [ ] Unique constraint on `(user_id, library_id)`
- [ ] Index on `(user_id, library_id)` for fast lookups - [ ] Index on `(user_id, library_id)` for fast lookups
**For `collections` table modifications:**
- [ ] `user_id UUID NULL REFERENCES users(id)` added (NULL for system collections)
- [ ] `show_on_dashboard BOOLEAN DEFAULT false` added
- [ ] `query_type TEXT DEFAULT 'filter'` added
- [ ] `priority INT DEFAULT 100` added
- [ ] `is_system_collection BOOLEAN DEFAULT false` added
- [ ] Index created on `(user_id, show_on_dashboard, priority)` WHERE show_on_dashboard = true
- [ ] 4 system collections pre-seeded with user_id = NULL:
- [ ] `continue-reading` (priority 1, query_type='continue-reading')
- [ ] `recently-added` (priority 2, query_type='recently-added')
- [ ] `recently-read` (priority 3, query_type='recently-read')
- [ ] `not-started` (priority 4, query_type='not-started')
**For `collections.show_on_dashboard` column:** **For `collections.show_on_dashboard` column:**
- [ ] Column added with `ALTER TABLE collections ADD COLUMN` - [ ] Column added with `ALTER TABLE collections ADD COLUMN`
- [ ] `IF NOT EXISTS` clause included - [ ] `IF NOT EXISTS` clause included
- [ ] Default value is `false` - [ ] Default value is `false`
- [ ] Index created on `(user_id, show_on_dashboard) WHERE show_on_dashboard = true` - [ ] Index created on `(user_id, show_on_dashboard, priority) WHERE show_on_dashboard = true
**For `collection_items.excluded` column:** **For `collection_items.excluded` column:**
@@ -190,31 +200,14 @@ dropdb test_bookhoard
- [ ] Index created on `(collection_id, excluded) WHERE excluded = true` - [ ] Index created on `(collection_id, excluded) WHERE excluded = true`
- [ ] Allows users to exclude auto-assigned items from filter-based collections - [ ] Allows users to exclude auto-assigned items from filter-based collections
**For `smart_section_types`:**
- [ ] All required columns exist:
- [ ] `id SERIAL PRIMARY KEY`
- [ ] `section_key TEXT UNIQUE NOT NULL`
- [ ] `title TEXT NOT NULL`
- [ ] `description TEXT`
- [ ] `icon TEXT`
- [ ] `default_priority INT`
- [ ] `is_global BOOLEAN DEFAULT false`
- [ ] Default sections inserted:
- [ ] `continue-reading` (priority 1, is_global=false)
- [ ] `recently-added` (priority 2, is_global=true)
- [ ] `recently-read` (priority 3, is_global=false)
- [ ] `unread` (priority 4, is_global=false)
**Verification:** **Verification:**
```bash ```bash
# Check table definitions # Check table definitions
psql bookhoard -c "\d user_dashboard_preferences" psql bookhoard -c "\d user_dashboard_preferences"
psql bookhoard -c "\d smart_section_types" psql bookhoard -c "\d collections" | grep -E "show_on_dashboard|query_type|priority|is_system_collection"
psql bookhoard -c "\d collections" | grep show_on_dashboard
# Check default data # Check system collections exist
psql bookhoard -c "SELECT * FROM smart_section_types ORDER BY default_priority" psql bookhoard -c "SELECT name, query_type, priority, is_system_collection FROM collections WHERE user_id IS NULL"
``` ```
### 2.3 Verify No Migration Files ### 2.3 Verify No Migration Files
@@ -281,12 +274,12 @@ rg "import.*net/http" internal/services/dashboard_service.go
- [ ] `filterHiddenSections(items []SectionItems, hidden []string) []SectionItems` - [ ] `filterHiddenSections(items []SectionItems, hidden []string) []SectionItems`
- [ ] `reorderSections(items []SectionItems, order []string) []SectionItems` - [ ] `reorderSections(items []SectionItems, order []string) []SectionItems`
- [ ] `getContinueReading(ctx, userID, libraryID, limit) ([]MediaItems, error)` - [ ] `getContinueReading(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getInProgress(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getRecentlyAdded(ctx, libraryID, limit) ([]MediaItems, error)` - [ ] `getRecentlyAdded(ctx, libraryID, limit) ([]MediaItems, error)`
- [ ] `getRecentlyRead(ctx, userID, libraryID, limit) ([]MediaItems, error)` - [ ] `getRecentlyRead(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getUnread(ctx, userID, libraryID, limit) ([]MediaItems, error)` - [ ] `getNotStarted(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getCollectionSections(ctx, userID, libraryID, limit) ([]SectionItems, error)` - [ ] `getCollectionSections(ctx, userID, libraryID, limit) ([]SectionItems, error)`
- [ ] `GetDashboardPreferences(ctx, userID, libraryID) (UserDashboardPreferences, error)` - [ ] `GetDashboardPreferences(ctx, userID, libraryID) (UserDashboardPreferences, error)`
- [ ] `RestoreSystemCollection(ctx, userID, collectionName) error`
**Verification:** **Verification:**
```bash ```bash
@@ -305,7 +298,7 @@ rg "GetSectionItems.*\[\]SectionItems" internal/services/dashboard_service.go
- [ ] **Recently Added**: ORDER BY created_at DESC - [ ] **Recently Added**: ORDER BY created_at DESC
- [ ] **Recently Read**: Progress >= 1 (completed) - [ ] **Recently Read**: Progress >= 1 (completed)
- [ ] **Not Started**: Progress = 0 OR no reading_progress record - [ ] **Not Started**: Progress = 0 OR no reading_progress record
- [ ] **Collections**: WHERE show_on_dashboard = true - [ ] **User Collections**: WHERE show_on_dashboard = true AND is_system_collection = false
**Check:** **Check:**
```bash ```bash
@@ -319,20 +312,20 @@ rg "ORDER BY" internal/services/dashboard_service.go
### 3.4 Verify User Preference Logic ### 3.4 Verify User Preference Logic
**Filter hidden sections:** **Filter hidden collections:**
- [ ] Empty hidden list returns all sections - [ ] Empty hidden list returns all collections
- [ ] Non-empty hidden list filters matching sections - [ ] Non-empty hidden list filters matching collections
- [ ] Comparison is case-sensitive - [ ] Comparison is case-sensitive
- [ ] No errors on empty section list - [ ] No errors on empty collection list
**Reorder sections:** **Reorder collections:**
- [ ] Empty order returns sections as-is - [ ] Empty order returns collections as-is
- [ ] Ordered sections come first - [ ] Ordered collections come first
- [ ] Unordered sections appended at end - [ ] Unordered collections appended at end
- [ ] No sections are lost - [ ] No collections are lost
- [ ] No duplicate sections in result - [ ] No duplicate collections in result
**Verification:** **Verification:**
```bash ```bash
@@ -459,15 +452,16 @@ rg "GetDashboardPreferences|UpsertDashboardPreferences|UpdateDashboardPreference
- [ ] Returns inserted/updated row - [ ] Returns inserted/updated row
**For `GetCollectionsForDashboard`:** **For `GetCollectionsForDashboard`:**
- [ ] Filters on `user_id` - [ ] Two separate queries for system and user collections
- [ ] Filters on `show_on_dashboard = true` - [ ] System collections: WHERE user_id IS NULL AND show_on_dashboard = true
- [ ] Orders by `created_at DESC` - [ ] User collections: WHERE user_id = $1 AND show_on_dashboard = true AND is_system_collection = false
- [ ] Both ordered by priority ASC
- [ ] Returns multiple rows - [ ] Returns multiple rows
**For `SetCollectionDashboardVisibility`:** **For `RestoreSystemCollection`:**
- [ ] INSERTs on conflict with `id` - [ ] Deletes user-owned copy of system collection
- [ ] Updates `show_on_dashboard` column - [ ] WHERE user_id = $1 AND name = $2 AND is_system_collection = true
- [ ] Returns modified row - [ ] System collection (user_id = NULL) automatically appears after deletion
**Verification:** **Verification:**
```bash ```bash
@@ -552,13 +546,13 @@ rg "c\.Get\(\"user\"\)" internal/handlers/dashboard.go
- [ ] Returns JSON object with `sections` array - [ ] Returns JSON object with `sections` array
- [ ] Each section has: - [ ] Each section has:
- [ ] `id` (section key) - [ ] `id` (collection key or name)
- [ ] `type` ("smart" or "collection") - [ ] `type` ("system" or "user")
- [ ] `title` - [ ] `title`
- [ ] `icon` - [ ] `icon`
- [ ] `items` (array of books) - [ ] `items` (array of books)
- [ ] `view_all_url` - [ ] `view_all_url` (empty for user collections)
- [ ] Each book has: - [ ] `priority`
- [ ] `id` (UUID string) - [ ] `id` (UUID string)
- [ ] `title` - [ ] `title`
- [ ] `author` - [ ] `author`
@@ -594,18 +588,18 @@ cd bruno/dashboard/
- [ ] `getSectionIcon(key string) string` - [ ] `getSectionIcon(key string) string`
- [ ] `getSectionViewAllURL(key string) string` - [ ] `getSectionViewAllURL(key string) string`
**Smart sections mapping:** **System collections mapping:**
- [ ] `continue-reading` → type: "smart", title: "Continue Reading", icon: "📖" - [ ] `continue-reading` → type: "system", title: "Continue Reading", icon: "📖"
- [ ] `recently-added` → type: "smart", title: "Recently Added", icon: "🆕" - [ ] `recently-added` → type: "system", title: "Recently Added", icon: "🆕"
- [ ] `recently-read` → type: "smart", title: "Recently Read", icon: "✅" - [ ] `recently-read` → type: "system", title: "Recently Read", icon: "✅"
- [ ] `unread` → type: "smart", title: "Not Started", icon: "📕" - [ ] `not-started` → type: "system", title: "Not Started", icon: "📕"
**Collections:** **User collections:**
- [ ] Non-smart sections → type: "collection" - [ ] Non-system collections → type: "user"
- [ ] Title uses collection name - [ ] Title uses collection name
- [ ] Icon defaults to "📚" - [ ] Icon uses collection icon
- [ ] view_all_url is empty string - [ ] view_all_url is empty string
**Verification:** **Verification:**
@@ -947,24 +941,25 @@ rg '<script src=' templates/dashboard.templ
### 8.2 Verify Component Templates ### 8.2 Verify Component Templates
**For `SectionCarousel`:** **For `CollectionCarousel`:**
- [ ] Accepts **handler type** parameter (e.g., `handlers.SectionData`) - [ ] Accepts **handler type** parameter (e.g., `handlers.SectionData`)
- [ ] **OR** accepts database type (e.g., `[]database.MediaItems`) - [ ] **OR** accepts database type (e.g., `[]database.MediaItems`)
- [ ] **NOT** `templates.SectionData` (violates guidelines) - [ ] **NOT** `templates.SectionData` (violates guidelines)
- [ ] Renders section header (title, icon, view-all link) - [ ] Renders collection header (title, icon, view-all link)
- [ ] Renders carousel container - [ ] Renders carousel container
- [ ] Navigation buttons (left/right) - [ ] Navigation buttons (left/right)
- [ ] Carousel track (overflow-x-auto) - [ ] Carousel track (overflow-x-auto)
- [ ] Book cards (snap-start) - [ ] Book cards (snap-start)
- [ ] Data attributes: - [ ] Data attributes:
- [ ] `data-section-id` - [ ] `data-collection-id`
- [ ] `data-section-type` - [ ] `data-collection-type`
- [ ] `data-action="scroll-carousel"` - [ ] `data-action="scroll-carousel"`
- [ ] Accessibility attributes: - [ ] Accessibility attributes:
- [ ] `aria-label` on nav buttons - [ ] `aria-label` on nav buttons
- [ ] `tabindex="0"` on book cards - [ ] `tabindex="0"` on book cards
- [ ] `role="button"` on book cards - [ ] `role="button"` on book cards
- [ ] For system collections: Shows "Restore" button
**For `BookCard`:** **For `BookCard`:**
@@ -984,13 +979,15 @@ rg '<script src=' templates/dashboard.templ
**For `DashboardSettingsModal`:** **For `DashboardSettingsModal`:**
- [ ] Fixed overlay with backdrop - [ ] Fixed overlay with backdrop
- [ ] Draggable section list - [ ] Draggable collection list
- [ ] Toggle switches for visibility - [ ] Toggle switches for visibility
- [ ] Items per section slider - [ ] Per-collection "Restore" buttons for system collections
- [ ] Items per collection slider
- [ ] Save/Cancel buttons - [ ] Save/Cancel buttons
- [ ] Data attributes: - [ ] Data attributes:
- [ ] `data-action="close-dashboard-settings"` - [ ] `data-action="close-dashboard-settings"`
- [ ] `data-action="toggle-section-visibility"` - [ ] `data-action="toggle-collection-visibility"`
- [ ] `data-action="restore-system-collection"` (for system collections)
- [ ] `data-action="update-items-count"` - [ ] `data-action="update-items-count"`
- [ ] `data-action="save-dashboard-settings"` - [ ] `data-action="save-dashboard-settings"`
@@ -1203,19 +1200,18 @@ rg "api\.|showToast\.|events\." web/src/dashboard.ts
**Required functions:** **Required functions:**
- [ ] `scrollCarousel(sectionId: string, direction: number): void` - [ ] `scrollCarousel(collectionId: string, direction: number): void`
- [ ] `switchLibrary(libraryId: string): Promise<void>` - Fetches JSON and re-renders sections - [ ] `switchLibrary(libraryId: string): Promise<void>` - Fetches JSON and re-renders collections
- [ ] `renderSections(sections: SectionData[]): void` - Renders sections from JSON - [ ] `renderCollections(sections: SectionData[]): void` - Renders collections from JSON
- [ ] `renderBookCard(book: BookInfo): string` - Renders single book card HTML - [ ] `renderBookCard(book: BookInfo): string` - Renders single book card HTML
- [ ] `openDashboardSettings(): void` - [ ] `openDashboardSettings(): void`
- [ ] `closeDashboardSettings(): void` - [ ] `closeDashboardSettings(): void`
- [ ] `saveDashboardSettings(): Promise<void>` - [ ] `saveDashboardSettings(): Promise<void>`
- [ ] `toggleSectionVisibility(sectionId: string): void` - [ ] `toggleCollectionVisibility(collectionId: string): void`
- [ ] `restoreSystemCollection(collectionName: string, collectionTitle: string): Promise<void>`
- [ ] `viewBook(bookId: string): Promise<void>` - [ ] `viewBook(bookId: string): Promise<void>`
- [ ] `reloadPage(): void` - [ ] `reloadPage(): void`
- [ ] `updateItemsCount(count: number): void` - [ ] `updateItemsCount(count: number): void`
- [ ] `viewBook(bookId: string): void`
- [ ] `reloadPage(): void`
**Behavior verification:** **Behavior verification:**
@@ -1454,13 +1450,29 @@ rg "interface BookInfo" web/src/types/dashboard.d.ts -A 30
# Step 3: Manually compare - Go should have same fields as TypeScript # Step 3: Manually compare - Go should have same fields as TypeScript
# Example verification: # Example verification:
# Go handler struct (internal/handlers/collections.go): # Go handler struct (internal/handlers/dashboard.go):
# type BookInfo struct { # type SectionData struct {
# MediaItemID pgtype.UUID `json:"media_item_id"` # ID string `json:"id"`
# Title pgtype.Text `json:"title"` # Type string `json:"type"`
# Author pgtype.Text `json:"author"` # Title string `json:"title"`
# CoverImagePath pgtype.Text `json:"cover_image_path"` # Description pgtype.Text `json:"description"`
# CreatedAt pgtype.Timestamp `json:"created_at"` # Icon string `json:"icon"`
# Items []BookInfo `json:"items"`
# ViewAllURL string `json:"view_all_url"`
# Priority int `json:"priority"`
# }
# // 8 fields total
# TypeScript interface (web/src/types/dashboard.d.ts) - MUST HAVE ALL 8 FIELDS:
# interface SectionData {
# id: string;
# type: string; // "system" or "user"
# title: string;
# description: string;
# icon: string;
# items: BookInfo[];
# view_all_url: string;
# priority: number;
# } # }
# TypeScript interface (web/src/types/dashboard.d.ts) - MUST HAVE ALL 5 FIELDS: # TypeScript interface (web/src/types/dashboard.d.ts) - MUST HAVE ALL 5 FIELDS:
@@ -1522,37 +1534,31 @@ type SectionData struct {
Items []BookInfo `json:"items"` Items []BookInfo `json:"items"`
ViewAllURL string `json:"view_all_url"` ViewAllURL string `json:"view_all_url"`
Priority int `json:"priority"` Priority int `json:"priority"`
IsHidden bool `json:"is_hidden"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
// 11 fields total // 8 fields total
``` ```
```typescript ```typescript
// TypeScript (web/src/types/dashboard.d.ts) // TypeScript (web/src/types/dashboard.d.ts)
// ✅ CORRECT - All 11 fields present // ✅ CORRECT - All 8 fields present
interface SectionData { interface SectionData {
id: string; id: string;
type: string; type: string;
title: string; title: string;
description: string | undefined; // pgtype.Text description: string;
icon: string; icon: string;
items: BookInfo[]; items: BookInfo[];
view_all_url: string; view_all_url: string;
priority: number; priority: number;
is_hidden: boolean;
created_at: string; // pgtype.Timestamp
updated_at: string; // pgtype.Timestamp
} }
// ❌ WRONG - Missing 4 fields (partial type, breaks type safety) // ❌ WRONG - Missing fields (partial type, breaks type safety)
interface SectionData { interface SectionData {
id: string; id: string;
type: string; type: string;
title: string; title: string;
items: BookInfo[]; items: BookInfo[];
// Missing: description, icon, view_all_url, priority, is_hidden, created_at, updated_at // Missing: description, icon, view_all_url, priority
} }
``` ```
@@ -2184,27 +2190,25 @@ curl -H "Authorization: Bearer $TOKEN" \
# API response example: # API response example:
# { # {
# "id": "continue-reading", # "id": "continue-reading",
# "type": "smart", # "type": "system",
# "title": "Continue Reading", # "title": "Continue Reading",
# "description": "Books you're currently reading", # "description": "Books you're currently reading",
# "icon": "📖", # "icon": "📖",
# "items": [...], # "items": [...],
# "view_all_url": "/section/continue-reading", # "view_all_url": "/section/continue-reading",
# "priority": 1, # "priority": 1
# "is_hidden": false
# } # }
# TypeScript interface MUST have all 9 fields above # TypeScript interface MUST have all 8 fields above
interface SectionData { interface SectionData {
id: string; id: string;
type: string; type: string; // "system" or "user"
title: string; title: string;
description: string; description: string;
icon: string; icon: string;
items: BookCardData[]; items: BookInfo[];
view_all_url: string; view_all_url: string;
priority: number; priority: number;
is_hidden: boolean;
// Missing any of these = CRITICAL TYPE SAFETY ISSUE // Missing any of these = CRITICAL TYPE SAFETY ISSUE
} }
@@ -2548,33 +2552,16 @@ rg "refactor|rewrite|breaking" CAROUSEL_DASHBOARD_PLAN.md -i
// Matches handlers.SectionData from internal/handlers/dashboard.go // Matches handlers.SectionData from internal/handlers/dashboard.go
// JSON response from /api/dashboard/sections // JSON response from /api/dashboard/sections
// All 11 fields from Go struct included // All 8 fields from Go struct included
export interface SectionData { export interface SectionData {
id: string; // Section identifier id: string; // Section identifier
type: string; // "smart" or "collection" type: string; // "system" or "user"
title: string; // Section display title title: string; // Section display title
description: string; // Section description description: string; // Section description
icon: string; // Section icon (emoji) icon: string; // Section icon (emoji)
items: BookCardData[]; // Books in this section items: BookInfo[]; // Books in this section
view_all_url: string; // Link to view all items view_all_url: string; // Link to view all items
priority: number; // Display order priority priority: number; // Display order priority
is_hidden: boolean; // User visibility preference
created_at: string; // ISO datetime when created
updated_at: string; // ISO datetime when last updated
}
// Matches handlers.BookInfo from internal/handlers/collections.go
// Used in: SectionData.items
// All 8 fields from Go struct included
export interface BookInfo {
media_item_id: string; // Book unique identifier
title: string; // Book title
author: string; // Author name
cover_image_path: string; // Path to cover image
library_id: string; // Library identifier
library_name: string; // Library display name
created_at: string; // ISO datetime when added
updated_at: string; // ISO datetime when last modified
} }
``` ```