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 {
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"
description: string; // matches Go's json:"description"
icon: string; // matches Go's json:"icon"
items: BookInfo[]; // matches Go's json:"items"
view_all_url: string; // matches Go's json:"view_all_url"
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
@@ -46,7 +43,7 @@ interface SectionData {
type: string;
title: string;
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
}
```
@@ -167,20 +164,33 @@ dropdb test_bookhoard
- [ ] `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`
- [ ] `user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE`
- [ ] `library_id UUID REFERENCES libraries(id) ON DELETE CASCADE`
- [ ] `hidden_sections TEXT[] DEFAULT '{}'`
- [ ] `section_order TEXT[] DEFAULT '{}'`
- [ ] `hidden_collections TEXT[] DEFAULT '{}'`
- [ ] `collection_order TEXT[] DEFAULT '{}'`
- [ ] `items_per_section INT DEFAULT 20`
- [ ] `created_at TIMESTAMP DEFAULT NOW()`
- [ ] `updated_at TIMESTAMP DEFAULT NOW()`
- [ ] Unique constraint on `(user_id, library_id)`
- [ ] Index on `(user_id, library_id)` for fast lookups
- [ ] Unique constraint on `(user_id, library_id)`
- [ ] 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:**
- [ ] Column added with `ALTER TABLE collections ADD COLUMN`
- [ ] `IF NOT EXISTS` clause included
- [ ] 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:**
@@ -190,31 +200,14 @@ dropdb test_bookhoard
- [ ] Index created on `(collection_id, excluded) WHERE excluded = true`
- [ ] 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:**
```bash
# Check table definitions
psql bookhoard -c "\d user_dashboard_preferences"
psql bookhoard -c "\d smart_section_types"
psql bookhoard -c "\d collections" | grep show_on_dashboard
psql bookhoard -c "\d collections" | grep -E "show_on_dashboard|query_type|priority|is_system_collection"
# Check default data
psql bookhoard -c "SELECT * FROM smart_section_types ORDER BY default_priority"
# Check system collections exist
psql bookhoard -c "SELECT name, query_type, priority, is_system_collection FROM collections WHERE user_id IS NULL"
```
### 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`
- [ ] `reorderSections(items []SectionItems, order []string) []SectionItems`
- [ ] `getContinueReading(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getInProgress(ctx, userID, libraryID, limit) ([]MediaItems, error)`
- [ ] `getRecentlyAdded(ctx, 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)`
- [ ] `GetDashboardPreferences(ctx, userID, libraryID) (UserDashboardPreferences, error)`
- [ ] `RestoreSystemCollection(ctx, userID, collectionName) error`
**Verification:**
```bash
@@ -305,7 +298,7 @@ rg "GetSectionItems.*\[\]SectionItems" internal/services/dashboard_service.go
- [ ] **Recently Added**: ORDER BY created_at DESC
- [ ] **Recently Read**: Progress >= 1 (completed)
- [ ] **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:**
```bash
@@ -319,20 +312,20 @@ rg "ORDER BY" internal/services/dashboard_service.go
### 3.4 Verify User Preference Logic
**Filter hidden sections:**
**Filter hidden collections:**
- [ ] Empty hidden list returns all sections
- [ ] Non-empty hidden list filters matching sections
- [ ] Empty hidden list returns all collections
- [ ] Non-empty hidden list filters matching collections
- [ ] 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
- [ ] Ordered sections come first
- [ ] Unordered sections appended at end
- [ ] No sections are lost
- [ ] No duplicate sections in result
- [ ] Empty order returns collections as-is
- [ ] Ordered collections come first
- [ ] Unordered collections appended at end
- [ ] No collections are lost
- [ ] No duplicate collections in result
**Verification:**
```bash
@@ -459,15 +452,16 @@ rg "GetDashboardPreferences|UpsertDashboardPreferences|UpdateDashboardPreference
- [ ] Returns inserted/updated row
**For `GetCollectionsForDashboard`:**
- [ ] Filters on `user_id`
- [ ] Filters on `show_on_dashboard = true`
- [ ] Orders by `created_at DESC`
- [ ] Two separate queries for system and user collections
- [ ] System collections: WHERE user_id IS NULL AND show_on_dashboard = true
- [ ] User collections: WHERE user_id = $1 AND show_on_dashboard = true AND is_system_collection = false
- [ ] Both ordered by priority ASC
- [ ] Returns multiple rows
**For `SetCollectionDashboardVisibility`:**
- [ ] INSERTs on conflict with `id`
- [ ] Updates `show_on_dashboard` column
- [ ] Returns modified row
**For `RestoreSystemCollection`:**
- [ ] Deletes user-owned copy of system collection
- [ ] WHERE user_id = $1 AND name = $2 AND is_system_collection = true
- [ ] System collection (user_id = NULL) automatically appears after deletion
**Verification:**
```bash
@@ -552,13 +546,13 @@ rg "c\.Get\(\"user\"\)" internal/handlers/dashboard.go
- [ ] Returns JSON object with `sections` array
- [ ] Each section has:
- [ ] `id` (section key)
- [ ] `type` ("smart" or "collection")
- [ ] `id` (collection key or name)
- [ ] `type` ("system" or "user")
- [ ] `title`
- [ ] `icon`
- [ ] `items` (array of books)
- [ ] `view_all_url`
- [ ] Each book has:
- [ ] `view_all_url` (empty for user collections)
- [ ] `priority`
- [ ] `id` (UUID string)
- [ ] `title`
- [ ] `author`
@@ -594,18 +588,18 @@ cd bruno/dashboard/
- [ ] `getSectionIcon(key string) string`
- [ ] `getSectionViewAllURL(key string) string`
**Smart sections mapping:**
**System collections mapping:**
- [ ] `continue-reading` → type: "smart", title: "Continue Reading", icon: "📖"
- [ ] `recently-added` → type: "smart", title: "Recently Added", icon: "🆕"
- [ ] `recently-read` → type: "smart", title: "Recently Read", icon: "✅"
- [ ] `unread` → type: "smart", title: "Not Started", icon: "📕"
- [ ] `continue-reading` → type: "system", title: "Continue Reading", icon: "📖"
- [ ] `recently-added` → type: "system", title: "Recently Added", icon: "🆕"
- [ ] `recently-read` → type: "system", title: "Recently Read", 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
- [ ] Icon defaults to "📚"
- [ ] Icon uses collection icon
- [ ] view_all_url is empty string
**Verification:**
@@ -947,24 +941,25 @@ rg '<script src=' templates/dashboard.templ
### 8.2 Verify Component Templates
**For `SectionCarousel`:**
**For `CollectionCarousel`:**
- [ ] Accepts **handler type** parameter (e.g., `handlers.SectionData`)
- [ ] **OR** accepts database type (e.g., `[]database.MediaItems`)
- [ ] **NOT** `templates.SectionData` (violates guidelines)
- [ ] Renders section header (title, icon, view-all link)
- [ ] Renders collection header (title, icon, view-all link)
- [ ] Renders carousel container
- [ ] Navigation buttons (left/right)
- [ ] Carousel track (overflow-x-auto)
- [ ] Book cards (snap-start)
- [ ] Data attributes:
- [ ] `data-section-id`
- [ ] `data-section-type`
- [ ] `data-collection-id`
- [ ] `data-collection-type`
- [ ] `data-action="scroll-carousel"`
- [ ] Accessibility attributes:
- [ ] `aria-label` on nav buttons
- [ ] `tabindex="0"` on book cards
- [ ] `role="button"` on book cards
- [ ] For system collections: Shows "Restore" button
**For `BookCard`:**
@@ -984,13 +979,15 @@ rg '<script src=' templates/dashboard.templ
**For `DashboardSettingsModal`:**
- [ ] Fixed overlay with backdrop
- [ ] Draggable section list
- [ ] Draggable collection list
- [ ] Toggle switches for visibility
- [ ] Items per section slider
- [ ] Per-collection "Restore" buttons for system collections
- [ ] Items per collection slider
- [ ] Save/Cancel buttons
- [ ] Data attributes:
- [ ] `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="save-dashboard-settings"`
@@ -1203,19 +1200,18 @@ rg "api\.|showToast\.|events\." web/src/dashboard.ts
**Required functions:**
- [ ] `scrollCarousel(sectionId: string, direction: number): void`
- [ ] `switchLibrary(libraryId: string): Promise<void>` - Fetches JSON and re-renders sections
- [ ] `renderSections(sections: SectionData[]): void` - Renders sections from JSON
- [ ] `scrollCarousel(collectionId: string, direction: number): void`
- [ ] `switchLibrary(libraryId: string): Promise<void>` - Fetches JSON and re-renders collections
- [ ] `renderCollections(sections: SectionData[]): void` - Renders collections from JSON
- [ ] `renderBookCard(book: BookInfo): string` - Renders single book card HTML
- [ ] `openDashboardSettings(): void`
- [ ] `closeDashboardSettings(): void`
- [ ] `saveDashboardSettings(): Promise<void>`
- [ ] `toggleSectionVisibility(sectionId: string): void`
- [ ] `toggleCollectionVisibility(collectionId: string): void`
- [ ] `restoreSystemCollection(collectionName: string, collectionTitle: string): Promise<void>`
- [ ] `viewBook(bookId: string): Promise<void>`
- [ ] `reloadPage(): void`
- [ ] `updateItemsCount(count: number): void`
- [ ] `viewBook(bookId: string): void`
- [ ] `reloadPage(): void`
**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
# Example verification:
# Go handler struct (internal/handlers/collections.go):
# type BookInfo struct {
# MediaItemID pgtype.UUID `json:"media_item_id"`
# Title pgtype.Text `json:"title"`
# Author pgtype.Text `json:"author"`
# CoverImagePath pgtype.Text `json:"cover_image_path"`
# CreatedAt pgtype.Timestamp `json:"created_at"`
# Go handler struct (internal/handlers/dashboard.go):
# type SectionData struct {
# ID string `json:"id"`
# Type string `json:"type"`
# Title string `json:"title"`
# Description pgtype.Text `json:"description"`
# 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:
@@ -1522,37 +1534,31 @@ type SectionData struct {
Items []BookInfo `json:"items"`
ViewAllURL string `json:"view_all_url"`
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 (web/src/types/dashboard.d.ts)
// ✅ CORRECT - All 11 fields present
// ✅ CORRECT - All 8 fields present
interface SectionData {
id: string;
type: string;
title: string;
description: string | undefined; // pgtype.Text
description: string;
icon: string;
items: BookInfo[];
view_all_url: string;
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 {
id: string;
type: string;
title: string;
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:
# {
# "id": "continue-reading",
# "type": "smart",
# "type": "system",
# "title": "Continue Reading",
# "description": "Books you're currently reading",
# "icon": "📖",
# "items": [...],
# "view_all_url": "/section/continue-reading",
# "priority": 1,
# "is_hidden": false
# "priority": 1
# }
# TypeScript interface MUST have all 9 fields above
# TypeScript interface MUST have all 8 fields above
interface SectionData {
id: string;
type: string;
type: string; // "system" or "user"
title: string;
description: string;
icon: string;
items: BookCardData[];
items: BookInfo[];
view_all_url: string;
priority: number;
is_hidden: boolean;
// 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
// JSON response from /api/dashboard/sections
// All 11 fields from Go struct included
// All 8 fields from Go struct included
export interface SectionData {
id: string; // Section identifier
type: string; // "smart" or "collection"
type: string; // "system" or "user"
title: string; // Section display title
description: string; // Section description
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
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
}
```