diff --git a/TYPESCRIPT_CONVERSION_PLAN.md b/TYPESCRIPT_CONVERSION_PLAN.md deleted file mode 100644 index 1e8e364..0000000 --- a/TYPESCRIPT_CONVERSION_PLAN.md +++ /dev/null @@ -1,1666 +0,0 @@ -# TypeScript Conversion Plan for Bookhoard - -## Executive Summary - -Convert **~2,800 lines of inline JavaScript** across 14 template files into organized TypeScript modules while **preserving the existing Hybrid SSR architecture**. The web UI will remain a client of the JSON API, alongside Kobo, KOReader, and future plugins. - -**Core Strategy:** Server-Side Rendering (SSR) for initial page loads + TypeScript for CRUD operations via existing JSON API. - ---- - -## Current Architecture Analysis - -### Already Converted to TypeScript ✅ -| Source | Output | Lines | Purpose | -|--------|--------|-------|---------| -| `web/src/toast.ts` | `toast.js` | 226 | Toast notifications, HTMX/fetch interceptors | -| `web/src/theme.ts` | `theme.js` | 130 | Theme management, smooth scrolling | -| `web/src/header.ts` | `header.js` | 114 | Header dropdowns, theme/user menus | -| `web/src/device-management.ts` | `device-management.js` | 102 | Device token copy/regeneration | - -### Remaining Standalone JavaScript ❌ -| File | Lines | Purpose | -|------|-------|---------| -| `web/static/search.js` | 275 | Header search with keyboard navigation, debouncing | - -### Inline JavaScript in Templates (~2,800 lines) -| Template | Script Lines | Primary Functions | -|----------|--------------|-------------------| -| `dashboard.templ` | ~456 | Statistics, recent activity | -| `docs.templ` | ~392 | Documentation search, sidebar toggle | -| `devices.templ` | ~386 | Token regeneration, sync URL display | -| `collections.templ` | ~362 | Bulk operations, filtering | -| `unlinked_books.templ` | ~312 | Book matching, linking, bulk operations | -| `collection_rules.templ` | ~239 | Rule CRUD operations, testing | -| `admin_library.templ` | ~229 | Admin library scan | -| `bookshelf.templ` | ~200 | Book viewing, pagination | -| `api_explorer.templ` | ~103 | API testing, cURL generation | -| `admin.templ` | ~29 | Admin dashboard actions | -| `login.templ` | ~29 | Login theme selection | -| `index.templ` | ~24 | Landing page theme preview | -| `admin_profile.templ` | ~11 | Admin profile management | -| `progress.templ` | ~6 | Reading progress | -| `conflicts.templ` | 0 | **No inline JS - uses onclick handlers only** | -| `queue.templ` | 0 | **No inline JS - uses onclick handlers only** | -| `analytics.templ` | 0 | **No inline JS - uses onclick handlers only** | - -**Total: ~2,800 lines of inline JavaScript to convert** - -**Note:** `conflicts.templ`, `queue.templ`, and `analytics.templ` have NO `` in `` -- **Why:** Theme logic already exists in `web/src/theme.ts`, remove duplication -- **Verify:** Theme dropdown still works after cleanup - -**6.2 Verify Progressive Enhancement** -- Test all pages with JavaScript disabled -- Ensure forms submit with full page reload (see Progressive Enhancement Pattern below) -- Verify critical paths work without JS - -**6.3 Build Configuration** -- Update `tsconfig.json` with new file structure (if needed) -- Verify all `.ts` files compile to `.js` in `web/static/` -- Add source maps for debugging (optional) - -**6.4 Testing** -- Verify all features work with TypeScript -- Check keyboard navigation -- Test form submissions -- Validate error handling - -**6.5 Documentation Search Integration** -- Create `web/src/docs.ts` -- `toggleSidebar()` - sidebar visibility toggle for mobile -- Documentation search functionality: - - Initialize lunr.js search index - - Search input debouncing - - Display search results - - Navigate to search results -- Remove inline script from `docs.templ` -- **Note:** Template uses external libraries (lunr.js, lunr-flex.js) for search -- Keep it simple - no complex search UI, just basic functionality - -**Build Verification:** -- Run `npm run build:ts` to verify entire project compiles -- Confirm all `.js` files generated in `web/static/` -- No TypeScript compilation errors -- Full manual testing of all features -- Test documentation search functionality -- Verify sidebar toggle works on mobile - -**Deliverable:** Clean templates, all inline JavaScript replaced with TypeScript modules, documentation search functional - ---- - -## Code Style Guidelines - -### 1. Procedural/Imperative with Functional Techniques - -**✅ GOOD:** -```typescript -// Pure function, no classes -function createRuleItem(rule: Rule): HTMLElement { - const div = document.createElement('div'); - div.className = 'rule-item'; - div.innerHTML = renderRuleHTML(rule); - return div; -} - -// Event delegation (one listener) -document.addEventListener('click', (e) => { - const target = e.target as HTMLElement; - const deleteBtn = target.closest('[data-action="delete-rule"]'); - if (deleteBtn) { - const ruleId = deleteBtn.dataset.ruleId; - deleteRule(ruleId); - } -}); -``` - -**❌ BAD:** -```typescript -// Class-based, OOP -class RuleManager { - private rules: Rule[] = []; - constructor() { ... } - addRule(rule: Rule) { ... } -} -``` - -### 2. Type Definitions - -**Define TypeScript Interfaces Matching Go Handler JSON Tags:** -```typescript -// Matches internal/handlers/book_matching.go -// Check JSON tags: `json:"progress_id"`, `json:"device_id"`, etc. -// Reference: Find struct definition in Go handler file -interface UnlinkedBookData { - progress_id: string; - device_id: string; - device_name: string; - device_type: 'koreader' | 'kobo' | 'web'; - title_from_device: string; - file_path: string; - sha256: string; - last_sync_time: string; - confidence_score: number; - potential_matches: PotentialMatchData[]; -} - -interface PotentialMatchData { - media_item_id: string; - title: string; - author: string; - confidence: number; - cover_image_path?: string; -} -``` - -**Important: Database Rows vs Handler Structs** -Some endpoints return database rows directly (e.g., `SearchMediaItemsRow`), not handler-defined structs. Always check the endpoint's return statement: - -```go -// internal/handlers/media.go:SearchMediaItems() -return c.JSON(http.StatusOK, partialResults) // Returns []SearchMediaItemsRow from database -``` - -When in doubt, grep the endpoint function and check what it actually returns. - -**Only Create Frontend-Specific Types When Necessary:** -```typescript -// OK: Frontend-specific state -type SearchState = { - query: string; - results: MediaItem[]; - selectedIndex: number; -}; - -// OK: UI-specific config -type ToastType = 'error' | 'success' | 'info'; -``` - -### 3. Error Handling - -**Integrate with Toast System:** -```typescript -async function deleteRule(ruleId: string): Promise { - try { - const response = await (window as any).api.delete(`/collections/rules/${ruleId}`); - - if (!response.ok) { - const error = await response.json(); - (window as any).showToast.error(error.message || 'Failed to delete rule'); - return; - } - - (window as any).showToast.success('Rule deleted'); - removeRuleFromDOM(ruleId); - } catch (error) { - (window as any).showToast.error('Network error: Unable to connect to server'); - console.error('Delete rule error:', error); - } -} -``` - -### 4. API Client Pattern - -**Procedural API Client (No Classes):** -```typescript -// web/src/api.ts -function getAuthHeader(): string { - const token = localStorage.getItem('token'); - return token ? `Bearer ${token}` : ''; -} - -async function apiGet(url: string): Promise { - return fetch(`/api${url}`, { - headers: { - 'Authorization': getAuthHeader(), - 'Content-Type': 'application/json' - } - }); -} - -async function apiPost(url: string, data: unknown): Promise { - return fetch(`/api${url}`, { - method: 'POST', - headers: { - 'Authorization': getAuthHeader(), - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }); -} - -async function apiDelete(url: string): Promise { - return fetch(`/api${url}`, { - method: 'DELETE', - headers: { - 'Authorization': getAuthHeader() - } - }); -} - -async function apiPut(url: string, data: unknown): Promise { - return fetch(`/api${url}`, { - method: 'PUT', - headers: { - 'Authorization': getAuthHeader(), - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }); -} - -// Export to window for use in other modules -(window as any).api = { - get: apiGet, - post: apiPost, - delete: apiDelete, - put: apiPut -}; -``` - -### 5. Event Handler Pattern (Pragmatic Mix) - -**Use onclick for Simple Static Content:** -```html - - -``` -**When to use:** -- Static server-rendered HTML -- Simple function calls to well-named functions -- When you want explicit, readable HTML - -**Use data-action + Event Delegation for Dynamic Content:** -```html - - -``` - -```typescript -// Event delegation (one listener handles all dynamic items) -document.addEventListener('click', async (e) => { - const target = e.target as HTMLElement; - const deleteBtn = target.closest('[data-action="delete-rule"]'); - - if (deleteBtn) { - const ruleId = deleteBtn.dataset.ruleId; - if (ruleId && confirm('Are you sure you want to delete this rule?')) { - await deleteRule(ruleId); - } - } -}); -``` -**When to use:** -- Dynamic content (rows added after page load) -- Lists where every item needs the same handler -- When event delegation genuinely simplifies the code - -**Use Form Interception for Progressive Enhancement:** -```html - -
- - -
-``` -**When to use:** -- Forms that must work without JavaScript -- Progressive enhancement is required -- Critical user flows - -**❌ DON'T:** -- Mandate data-action everywhere (over-engineering) -- Remove all onclick handlers (unnecessary refactoring) - -**✅ DO:** -- Remove inline ` -``` - -### After: TypeScript Module (Browser Globals) - -**TypeScript Module:** -```typescript -// web/src/collections.ts - -interface Rule { - id: string; - field: string; - operator: string; - value: string; -} - -function escapeHtml(text: string): string { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -async function loadRules(collectionId: string): Promise { - try { - const response = await (window as any).api.get(`/collections/${collectionId}/rules`); - - if (!response.ok) { - throw new Error('Failed to load rules'); - } - - const rules: Rule[] = await response.json(); - renderRules(rules); - } catch (error) { - console.error('Load rules error:', error); - (window as any).showToast.error('Failed to load rules'); - } -} - -function renderRules(collectionId: string, rules: Rule[]): void { - const container = document.getElementById('rules-container'); - if (!container) return; - - container.innerHTML = rules.map(rule => ` -
- ${escapeHtml(rule.field)} - -
- `).join(''); -} - -async function loadRules(collectionId: string): Promise { - try { - const response = await (window as any).api.get(`/collections/${collectionId}/rules`); - - if (!response.ok) { - throw new Error('Failed to load rules'); - } - - const rules: Rule[] = await response.json(); - renderRules(collectionId, rules); - } catch (error) { - console.error('Load rules error:', error); - (window as any).showToast.error('Failed to load rules'); - } -} - -async function deleteRule(collectionId: string, ruleId: string): Promise { - if (!confirm('Are you sure you want to delete this rule?')) { - return; - } - - try { - const response = await (window as any).api.delete(`/collections/${collectionId}/rules/${ruleId}`); - - if (!response.ok) { - throw new Error('Failed to delete rule'); - } - - (window as any).showToast.success('Rule deleted'); - - const ruleElement = document.querySelector(`[data-rule-id="${ruleId}"]`); - ruleElement?.remove(); - } catch (error) { - console.error('Delete rule error:', error); - (window as any).showToast.error('Failed to delete rule'); - } -} - -// Export to window for HTML access -(window as any).loadRules = loadRules; -(window as any).deleteRule = deleteRule; - -// Initialize on page load -if (typeof document !== 'undefined') { - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializeCollectionRules); - } else { - initializeCollectionRules(); - } -} - -function initializeCollectionRules() { - const collectionId = document.body.dataset.collectionId; - if (collectionId) { - loadRules(collectionId); - } -} -``` - -**Cleaned Template:** -```html - - - - - - - - - - -
- - { range .Rules } -
- { .Field } - -
- { end } -
- - -``` - -**Benefits:** -- ✅ No inline JavaScript logic (functions, fetch calls, state management) -- ✅ Simple onclick calls are explicit and easy to understand -- ✅ Type-safe API calls through procedural api module -- ✅ Better error handling with toast integration -- ✅ Reusable module attached to window -- ✅ SSR still works (initial rules in HTML) -- ✅ Progressive enhancement (form can submit without JS) -- ✅ No ES modules or bundler needed (browser globals) - ---- - -*Generated: 2025-02-17* -*Updated: 2025-02-18 (Revised to match existing codebase patterns)* -*Updated: 2025-02-18 (Fixed file paths, removed unnecessary build scripts, added manual type sync strategy)* -*Updated: 2025-02-18 (Changed to .d.ts for type definitions, added build verification steps, documented progressive enhancement pattern)* -*Updated: 2025-02-18 (Corrected type definitions to use snake_case matching Go JSON tags, simplified progressive enhancement section, added missing types)* -*Updated: 2025-02-18 (Clarified database row vs handler struct confusion, documented template-handler type sharing, added HTMX progressive enhancement details, added common pitfalls appendix)* -*Updated: 2025-02-18 (Added missing templates: analytics, queue, conflicts, docs; added 10 missing type definitions; updated line counts to ~6,300; revised timeline to 20-25 days; added all missing phases and tasks)* -*Updated: 2025-02-18 (VERIFIED CORRECTIONS: Fixed line counts (~2,800 actual vs ~6,300 estimated), corrected analytics/queue/conflicts type definitions to match actual Go structs, noted HTMX forms lack action attributes, reduced timeline to 13-18 days)* -*Follows: PROJECT_GUIDELINES.md* -*Architecture: Hybrid SSR + TypeScript CRUD (Browser Globals, No ES Modules)* diff --git a/TYPESCRIPT_CONVERSION_VERIFICATION_CHECKLIST.md b/TYPESCRIPT_CONVERSION_VERIFICATION_CHECKLIST.md deleted file mode 100644 index 30df244..0000000 --- a/TYPESCRIPT_CONVERSION_VERIFICATION_CHECKLIST.md +++ /dev/null @@ -1,781 +0,0 @@ -# TypeScript Conversion Plan Verification Checklist - -Use this checklist to comprehensively audit the TypeScript Conversion Plan in a single pass. Each item includes verification steps to confirm accuracy. - ---- - -## 1. Type Definition Verification - -### 1.1 Verify All Types Match Actual API Responses - -**For each type definition in `web/src/types/api.d.ts` (planned):** - -- [ ] Locate the API endpoint in `internal/handlers/*.go` -- [ ] Check what the endpoint **actually returns** (return statement) -- [ ] Determine if it returns: - - Handler-defined struct (e.g., `handlers.BookInfo`) - - Database row (e.g., `database.SearchMediaItemsRow`) - - Map/slice of either -- [ ] Find the source type definition: - - Handler structs: `internal/handlers/*.go` - - Database rows: `internal/database/queries.sql.go` -- [ ] Verify all JSON tags match TypeScript interface fields (snake_case) -- [ ] Map pgtype fields to TypeScript types: - - `pgtype.Text` → `string | undefined` - - `pgtype.UUID` → `string` - - `pgtype.Timestamp` → `string` (ISO datetime) - - `pgtype.Numeric` → `number` or `string` - - `pgtype.Bool` → `boolean` - - `[]string` → `string[]` - -**Common Pitfalls:** -- Handler structs may be defined but unused (e.g., `search.go`'s `MediaItemSummary`) -- Endpoints may return database rows directly, not handler structs -- JSON tags may use snake_case while Go fields use PascalCase -- Arrays vs single values (e.g., `author` string vs `authors` array) - -**Verification Commands:** -```bash -# Find endpoint implementation -rg "func.*SearchMediaItems" internal/handlers/ - -# Check return type -rg -A 10 "func.*SearchMediaItems" internal/handlers/media.go | grep "return c.JSON" - -# Find type definition -rg "type SearchMediaItemsRow struct" internal/database/queries.sql.go - -# Check JSON tags -rg 'json:"' internal/database/queries.sql.go | grep "SearchMediaItemsRow" -``` - -### 1.2 Verify No Duplicate/Conflicting Type Definitions - -- [ ] No two TypeScript interfaces describe the same API response -- [ ] No interface fields contradict actual JSON response -- [ ] No unused handler structs are referenced in type comments -- [ ] All inline types in `.ts` files could move to `types/api.d.ts` - ---- - -## 2. API Contract Verification - -### 2.1 Verify All API Endpoints Referenced in Plan Exist - -**For each endpoint mentioned:** - -- [ ] Endpoint exists in `internal/handlers/*.go` -- [ ] Route is registered in routing code -- [ ] HTTP method matches (GET/POST/PUT/DELETE) -- [ ] Response format matches plan's type definitions -- [ ] Authentication requirements match plan assumptions - -**Check Commands:** -```bash -# Find endpoint definition -rg "POST.*collections.*rules" internal/handlers/ - -# Check route registration -rg "collections.*rules" cmd/server/ or internal/router/ -``` - -### 2.2 Verify Authentication Patterns - -- [ ] All API endpoints that require auth are documented -- [ ] Token storage approach matches (`localStorage.getItem('token')`) -- [ ] Auth header format consistent (`Bearer ${token}`) -- [ ] 401 handling documented (token clearing, redirect) - -### 2.3 Verify Error Response Formats - -- [ ] Error responses use consistent structure (`{"error": "message"}`) -- [ ] Toast integration documented for all error cases -- [ ] Network error handling documented -- [ ] Validation error handling documented (400 responses) - ---- - -## 3. Cross-Reference Verification - -### 3.1 Template-Handler Type Sharing - -**For each template that imports handlers:** - -- [ ] Template imports `internal/handlers` package -- [ ] Template uses handler types for SSR data (e.g., `handlers.BookInfo`) -- [ ] TypeScript interfaces match the same handler JSON responses -- [ ] No duplicate type definitions between handlers and templates -- [ ] Template-only types are clearly marked (e.g., `PageData`, `UnsafeHTML`) - -**Verification:** -```bash -# Find templates importing handlers -rg 'import.*handlers' templates/*.templ - -# Check handler type usage in templates -rg 'handlers\.(CollectionData|BookInfo|UserProfile)' templates/*.templ -``` - -### 3.2 Existing TypeScript Module Patterns - -**Verify all existing `.ts` modules follow documented patterns:** - -- [ ] Procedural style (no classes, no `this`) -- [ ] Functions exported to `window` object -- [ ] No ES module imports/exports (browser globals) -- [ ] Proper type annotations (`import type` for type-only imports) -- [ ] Consistent error handling with toast integration - -**Check existing modules:** -- `web/src/toast.ts` -- `web/src/theme.ts` -- `web/src/header.ts` -- `web/src/device-management.ts` - -### 3.3 JavaScript to TypeScript Mapping - -**For each inline script in templates:** - -- [ ] Identify all `