diff --git a/TYPESCRIPT_CONVERSION_PLAN.md b/TYPESCRIPT_CONVERSION_PLAN.md new file mode 100644 index 0000000..6776e54 --- /dev/null +++ b/TYPESCRIPT_CONVERSION_PLAN.md @@ -0,0 +1,992 @@ +# TypeScript Conversion Plan for Bookhoard + +## Executive Summary + +Convert **~5,500 lines of inline JavaScript** across 13 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` | ~100 | Header dropdowns, theme/user menus | +| `web/src/device-management.ts` | `device-management.js` | ~70 | 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 (~5,500 lines) +| Template | Script Lines | Primary Functions | +|----------|--------------|-------------------| +| `collection_rules.templ` | ~400 | Rule CRUD operations, testing | +| `unlinked_books.templ` | ~650 | Book matching, linking, bulk operations | +| `collections.templ` | ~500 | Bulk operations, filtering | +| `devices.templ` | ~350 | Token regeneration, sync URL display | +| `bookshelf.templ` | ~250 | Book viewing, pagination | +| `dashboard.templ` | ~300 | Statistics, recent activity | +| `api_explorer.templ` | ~200 | API testing, cURL generation | +| `admin_library.templ` | ~250 | Admin library scan | +| `admin_profile.templ` | ~150 | Admin profile management | +| `admin.templ` | ~150 | Admin dashboard actions | +| `index.templ` | ~100 | Landing page theme preview | +| `login.templ` | ~100 | Login theme selection | +| `progress.templ` | ~100 | Reading progress | + +**Total: ~5,500 lines of inline JavaScript to convert** + +--- + +## Architecture: Hybrid SSR + TypeScript CRUD + +### Current Pattern (Already Working) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User visits /collections │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 1. Go handler: GET /collections │ +│ 2. Fetches data from CollectionService │ +│ 3. Renders templates.Collection(user, collections) │ +│ 4. Returns complete HTML page with data │ +└─────────────────────────────────────────────────────────────┘ + ↓ + [Page displays instantly with data] + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 5. User clicks "Add Rule" button │ +│ 6. TypeScript: fetch POST /api/collections/{id}/rules │ +│ 7. Server returns JSON │ +│ 8. TypeScript updates DOM │ +└─────────────────────────────────────────────────────────────┘ +``` + +### What This Preserves + +✅ **Fast initial page loads** - SSR with data +✅ **Progressive enhancement** - Works without JavaScript +✅ **Single API** - All clients use `/api/*` endpoints +✅ **Service layer as source of truth** - All handlers use same services +✅ **No handler duplication** - No new HTML endpoints needed +✅ **Plugin ecosystem ready** - Plugins use JSON API + +--- + +## Proposed TypeScript Structure + +``` +web/ts/ +├── core/ +│ ├── toast.ts # ✅ Already converted +│ ├── theme.ts # ✅ Already converted +│ ├── storage.ts # NEW: localStorage wrapper with types +│ ├── dom.ts # NEW: DOM utilities (escapeHtml, querySelector) +│ └── api.ts # NEW: API client wrapper with auth +│ +├── features/ +│ ├── search/ +│ │ ├── search.ts # Convert from search.js +│ │ └── types.ts # Search result types (match Go handlers) +│ │ +│ ├── collections/ +│ │ ├── rules.ts # Convert from inline JS +│ │ ├── bulk.ts # Convert from inline JS +│ │ └── types.ts # Reuse handlers.Rule, handlers.Collection +│ │ +│ ├── linking/ +│ │ ├── matcher.ts # Convert from inline JS +│ │ ├── bulk-link.ts # Convert from inline JS +│ │ ├── manual-link.ts # Convert from inline JS +│ │ └── types.ts # Reuse handlers.BookMatch, handlers.Progress +│ │ +│ ├── devices/ +│ │ ├── token.ts # Convert from inline JS +│ │ └── types.ts # Reuse handlers.Device types +│ │ +│ ├── bookshelf/ +│ │ ├── display.ts # Convert from inline JS +│ │ ├── pagination.ts # Convert from inline JS +│ │ └── types.ts # Reuse handlers.MediaItem types +│ │ +│ ├── api-explorer/ +│ │ ├── request.ts # Convert from inline JS +│ │ ├── response.ts # Convert from inline JS +│ │ └── curl.ts # Convert from inline JS +│ │ +│ └── admin/ +│ ├── scan.ts # Convert from inline JS +│ └── stats.ts # Convert from inline JS +│ +└── shared/ + ├── events.ts # Event delegation utilities + └── auth.ts # Token management helpers +``` + +--- + +## Type Sharing Strategy + +### Principle: Reuse Go Handler Types + +**❌ DON'T DO THIS:** +```typescript +// Duplicating types from Go handlers +interface Collection { + id: string; + name: string; + // ... +} +``` + +**✅ DO THIS:** +```typescript +// Use types that match Go handlers exactly +// These types are already defined in Go and returned by /api/* +interface MediaItem { + id: string; + title: string; + author?: string; + library_id: string; + library_type_name: 'ebooks' | 'comics' | 'manga'; + cover_image_path?: string; +} + +// From handlers.CollectionData +interface CollectionData { + ID: string; + Name: string; + Description: string; + Color: string; + Icon: string; +} +``` + +**Why:** +- Single source of truth (Go handlers) +- API returns these types as JSON +- TypeScript matches exactly what server provides +- No duplication, no drift + +--- + +## Conversion Phases + +### Phase 1: Complete Standalone File Conversion +**Priority: High | Effort: 1 day | Dependencies: None** + +**Tasks:** +1. Convert `web/static/search.js` → `web/ts/features/search/search.ts` +2. Add proper types for search results (match `handlers.MediaItem`) +3. Extract keyboard navigation logic into pure functions +4. Update templates to use new TypeScript module + +**Deliverable:** All standalone JavaScript converted to TypeScript + +**Files:** +- Create: `web/ts/features/search/search.ts` +- Create: `web/ts/features/search/types.ts` +- Delete: `web/static/search.js` +- Update: Templates referencing search functions + +--- + +### Phase 2: Core Utilities (Shared Infrastructure) +**Priority: High | Effort: 1-2 days | Dependencies: None** + +**Tasks:** +1. Create `web/ts/core/storage.ts` + - localStorage wrapper with type safety + - Token management helpers + - Theme persistence + +2. Create `web/ts/core/dom.ts` + - escapeHtml utility + - querySelector wrappers with null checks + - Element creation helpers + +3. Create `web/ts/core/api.ts` + - API client wrapper + - Automatic auth header injection + - Error handling integration with toast system + +4. Create `web/ts/shared/events.ts` + - Event delegation helpers + - Data attribute selectors + - Common event handlers + +**Deliverable:** Reusable utilities for all feature modules + +**Files:** +- Create: `web/ts/core/storage.ts` +- Create: `web/ts/core/dom.ts` +- Create: `web/ts/core/api.ts` +- Create: `web/ts/shared/events.ts` + +--- + +### Phase 3: Low Complexity Features (Search, Header, Admin) +**Priority: Medium | Effort: 3-4 days | Dependencies: Phase 2** + +**Tasks:** + +**3.1 Search Module (from Phase 1)** +- Extract search logic from `search.ts` +- Add debounced search with proper types +- Keyboard navigation state management +- Integration with `/api/media-items/search` + +**3.2 Header Dropdowns** +- Extract from `header.ts` (already TS) +- Add event delegation for dropdowns +- Theme switching logic +- User menu interactions + +**3.3 Admin Actions** +- Quick scan trigger +- System stats display +- Admin profile updates + +**Deliverable:** Search, header, and admin features in TypeScript + +**Files:** +- Refine: `web/ts/features/search/search.ts` +- Update: `web/ts/features/search/types.ts` +- Create: `web/ts/features/admin/scan.ts` +- Create: `web/ts/features/admin/stats.ts` +- Update: `web/src/header.ts` (add event delegation) + +--- + +### Phase 4: Medium Complexity Features (Collections, Bookshelf, Devices) +**Priority: Medium | Effort: 4-5 days | Dependencies: Phase 2, 3** + +**Tasks:** + +**4.1 Collection Rules** +- Convert rule CRUD from `collection_rules.templ` +- Use `/api/collections/{id}/rules` endpoints +- Rule testing functionality +- Bulk rule operations + +**4.2 Bookshelf Display** +- Convert from `bookshelf.templ` +- Pagination logic +- Library selection state +- Book viewing interactions + +**4.3 Device Management** +- Convert from `devices.templ` +- Token regeneration +- Sync URL display +- Device registration + +**Deliverable:** Collections, bookshelf, and devices features in TypeScript + +**Files:** +- Create: `web/ts/features/collections/rules.ts` +- Create: `web/ts/features/collections/bulk.ts` +- Create: `web/ts/features/collections/types.ts` +- Create: `web/ts/features/bookshelf/display.ts` +- Create: `web/ts/features/bookshelf/pagination.ts` +- Create: `web/ts/features/devices/token.ts` +- Create: `web/ts/features/devices/types.ts` + +--- + +### Phase 5: High Complexity Features (Linking, Bulk Operations) +**Priority: Low | Effort: 5-6 days | Dependencies: Phase 4** + +**Tasks:** + +**5.1 Book Linking** +- Convert matching logic from `unlinked_books.templ` +- Search and match functionality +- Manual link modal +- Bulk auto-link +- Bulk get suggestions + +**5.2 API Explorer** +- Convert from `api_explorer.templ` +- Request/response display +- cURL generation +- History tracking + +**Deliverable:** Book linking and API explorer in TypeScript + +**Files:** +- Create: `web/ts/features/linking/matcher.ts` +- Create: `web/ts/features/linking/bulk-link.ts` +- Create: `web/ts/features/linking/manual-link.ts` +- Create: `web/ts/features/linking/types.ts` +- Create: `web/ts/features/api-explorer/request.ts` +- Create: `web/ts/features/api-explorer/response.ts` +- Create: `web/ts/features/api-explorer/curl.ts` + +--- + +### Phase 6: Template Integration & Cleanup +**Priority: High | Effort: 2-3 days | Dependencies: All phases** + +**Tasks:** + +**6.1 Update Templates** +- Replace ` +``` + +--- + +## Build & Deployment + +### TypeScript Configuration + +**Update `tsconfig.json`:** +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "none", + "lib": ["ES2020", "DOM"], + "outDir": "./web/static", + "rootDir": "./web/ts", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": false, + "sourceMap": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true + }, + "include": [ + "web/ts/**/*.ts", + "web/src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} +``` + +### Build Scripts + +**Update `package.json`:** +```json +{ + "scripts": { + "build:css": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --watch", + "build:css:prod": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --minify", + "build:ts": "tsc", + "build:ts:watch": "tsc --watch", + "build:ts:prod": "tsc --sourceMap false", + "build:all": "npm run build:ts && npm run build:css:prod", + "dev": "npm run build:ts:watch & npm run build:css" + } +} +``` + +### Output Structure + +**Compiled JavaScript:** +``` +web/static/ +├── core/ +│ ├── toast.js # ✅ Already exists +│ ├── theme.js # ✅ Already exists +│ ├── header.js # ✅ Already exists +│ ├── device-management.js # ✅ Already exists +│ ├── storage.js # NEW +│ ├── dom.js # NEW +│ └── api.js # NEW +├── features/ +│ ├── search/ +│ │ └── search.js # NEW +│ ├── collections/ +│ │ ├── rules.js # NEW +│ │ └── bulk.js # NEW +│ ├── linking/ +│ │ ├── matcher.js # NEW +│ │ ├── bulk-link.js # NEW +│ │ └── manual-link.js # NEW +│ ├── bookshelf/ +│ │ ├── display.js # NEW +│ │ └── pagination.js # NEW +│ ├── devices/ +│ │ └── token.js # NEW +│ ├── api-explorer/ +│ │ ├── request.js # NEW +│ │ ├── response.js # NEW +│ │ └── curl.js # NEW +│ └── admin/ +│ ├── scan.js # NEW +│ └── stats.js # NEW +└── shared/ + ├── events.js # NEW + └── auth.js # NEW +``` + +--- + +## Testing Strategy + +### 1. Unit Testing (Optional) +Test pure functions that don't depend on DOM: +```typescript +// Test utilities +describe('escapeHtml', () => { + it('should escape HTML entities', () => { + expect(escapeHtml(' +``` + +### After: TypeScript Module + Event Delegation + +**TypeScript Module:** +```typescript +// web/ts/features/collections/rules.ts +import { apiClient } from '../../core/api.js'; +import { escapeHtml } from '../../core/dom.js'; + +interface Rule { + id: string; + field: string; + operator: string; + value: string; +} + +function renderRules(rules: Rule[]): void { + const container = document.getElementById('rules-container'); + if (!container) return; + + container.innerHTML = rules.map(rule => ` +