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:
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user