diff --git a/TYPESCRIPT_CONVERSION_PLAN.md b/TYPESCRIPT_CONVERSION_PLAN.md
index 6776e54..1e8e364 100644
--- a/TYPESCRIPT_CONVERSION_PLAN.md
+++ b/TYPESCRIPT_CONVERSION_PLAN.md
@@ -2,7 +2,7 @@
## 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.
+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.
@@ -15,32 +15,38 @@ Convert **~5,500 lines of inline JavaScript** across 13 template files into orga
|--------|--------|-------|---------|
| `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 |
+| `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 (~5,500 lines)
+### Inline JavaScript in Templates (~2,800 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 |
+| `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: ~5,500 lines of inline JavaScript to convert**
+**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
+- 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
-- Configure build process for all modules
-- Add source maps for debugging
+- 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
@@ -360,7 +785,27 @@ interface CollectionData {
- Test form submissions
- Validate error handling
-**Deliverable:** Clean templates, all inline JavaScript replaced with TypeScript modules
+**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
---
@@ -401,25 +846,43 @@ class RuleManager {
### 2. Type Definitions
-**Reuse Go Handler Types:**
+**Define TypeScript Interfaces Matching Go Handler JSON Tags:**
```typescript
-// From internal/handlers/book_matching.go
+// 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 {
- ProgressID: string;
- DeviceID: string;
- TitleFromDevice: string;
- SHA256: string;
- PotentialMatches: PotentialMatchData[];
+ 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 {
- MediaItemID: string;
- Title: string;
- Author: string;
- Confidence: number;
+ 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
@@ -439,18 +902,18 @@ type ToastType = 'error' | 'success' | 'info';
```typescript
async function deleteRule(ruleId: string): Promise {
try {
- const response = await apiClient.delete(`/api/collections/rules/${ruleId}`);
+ const response = await (window as any).api.delete(`/collections/rules/${ruleId}`);
if (!response.ok) {
const error = await response.json();
- window.showToast.error(error.message || 'Failed to delete rule');
+ (window as any).showToast.error(error.message || 'Failed to delete rule');
return;
}
- window.showToast.success('Rule deleted');
+ (window as any).showToast.success('Rule deleted');
removeRuleFromDOM(ruleId);
} catch (error) {
- window.showToast.error('Network error: Unable to connect to server');
+ (window as any).showToast.error('Network error: Unable to connect to server');
console.error('Delete rule error:', error);
}
}
@@ -458,62 +921,83 @@ async function deleteRule(ruleId: string): Promise {
### 4. API Client Pattern
-**Centralized API Client:**
+**Procedural API Client (No Classes):**
```typescript
-// web/ts/core/api.ts
-class APIClient {
- private baseURL = '/api';
- private getAuthHeader(): string {
- const token = localStorage.getItem('token');
- return token ? `Bearer ${token}` : '';
- }
-
- async get(url: string): Promise {
- return fetch(`${this.baseURL}${url}`, {
- headers: {
- 'Authorization': this.getAuthHeader(),
- 'Content-Type': 'application/json'
- }
- });
- }
-
- async post(url: string, data: unknown): Promise {
- return fetch(`${this.baseURL}${url}`, {
- method: 'POST',
- headers: {
- 'Authorization': this.getAuthHeader(),
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data)
- });
- }
-
- async delete(url: string): Promise {
- return fetch(`${this.baseURL}${url}`, {
- method: 'DELETE',
- headers: {
- 'Authorization': this.getAuthHeader()
- }
- });
- }
+// web/src/api.ts
+function getAuthHeader(): string {
+ const token = localStorage.getItem('token');
+ return token ? `Bearer ${token}` : '';
}
-export const apiClient = new APIClient();
+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 Delegation Pattern
+### 5. Event Handler Pattern (Pragmatic Mix)
-**Replace Inline Handlers:**
+**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
+
```
-**Handle with Event Delegation:**
```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"]');
@@ -526,31 +1010,117 @@ document.addEventListener('click', async (e) => {
}
});
```
+**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 `
+
+
+
+```
+
+**How Current HTMX Forms Work:**
+- With JavaScript: HTMX intercepts submit, posts to API, updates DOM without reload
+- Without JavaScript: **Form does NOT submit** (no action attribute)
+- This is pre-existing behavior - not introduced by this conversion
+
+**When to Add TypeScript Form Interception:**
+Use TypeScript interception only when you need custom behavior beyond HTMX:
+
+```typescript
+// Example: Custom form handling with redirect
+// web/src/auth.ts
+document.addEventListener('DOMContentLoaded', () => {
+ const form = document.querySelector('form[action="/api/auth/login"]') as HTMLFormElement;
+ if (!form) return;
+
+ form.addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ const formData = new FormData(form);
+ const response = await fetch('/api/auth/login', {
+ method: 'POST',
+ body: formData
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ localStorage.setItem('token', data.access_token);
+ window.location.href = '/dashboard';
+ } else {
+ (window as any).showToast.error('Login failed');
+ }
+ });
+});
+```
+
+**Guidelines:**
+- Prefer HTMX for forms (already progressive enhanced)
+- Add TypeScript only when you need custom logic
+- All forms must have `action` and `method` attributes
+- Test with JavaScript disabled before completing conversion
+- HTMX: works without JS, enhanced with JS
+- Custom TypeScript: must handle both cases
+
---
## Build & Deployment
### TypeScript Configuration
-**Update `tsconfig.json`:**
+**Current `tsconfig.json` (already configured correctly):**
```json
{
"compilerOptions": {
@@ -558,21 +1128,19 @@ document.addEventListener('click', async (e) => {
"module": "none",
"lib": ["ES2020", "DOM"],
"outDir": "./web/static",
- "rootDir": "./web/ts",
+ "rootDir": "./web/src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": false,
- "sourceMap": true,
+ "sourceMap": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "resolveJsonModule": true
+ "noFallthroughCasesInSwitch": true
},
"include": [
- "web/ts/**/*.ts",
"web/src/**/*.ts"
],
"exclude": [
@@ -581,63 +1149,54 @@ document.addEventListener('click', async (e) => {
}
```
+**Key points:**
+- `module: "none"` = Browser globals (no ES modules)
+- `rootDir: "./web/src"` = All TypeScript in src/
+- `outDir: "./web/static"` = Flat output structure
+- No bundler needed - TypeScript compiles directly to browser-compatible JavaScript
+
### Build Scripts
-**Update `package.json`:**
+**Current `package.json` (no changes needed):**
```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"
+ "build:ts:watch": "tsc --watch"
}
}
```
+**Note:** No additional build scripts needed. Docker handles production builds, and `tsc` compilation is environment-agnostic. Run `npm run build:ts:watch` for development.
+
### Output Structure
-**Compiled JavaScript:**
+**Compiled JavaScript (flat structure in web/static/):**
```
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
+├── toast.js # ✅ Already exists
+├── theme.js # ✅ Already exists
+├── header.js # ✅ Already exists
+├── device-management.js # ✅ Already exists
+├── search.js # NEW (from search.js)
+├── storage.js # NEW
+├── dom.js # NEW
+├── api.js # NEW
+├── collections.js # NEW
+├── linking.js # NEW
+├── bookshelf.js # NEW
+├── api-explorer.js # NEW
+├── admin.js # NEW
+├── analytics.js # NEW
+├── queue.js # NEW
+├── conflicts.js # NEW
+└── docs.js # NEW
```
+**No subdirectories** - TypeScript with `module: "none"` compiles to flat output matching source structure.
+
---
## Testing Strategy
@@ -781,38 +1340,131 @@ git checkout backup-before-collections -- templates/collections.templ
|-------|----------|--------------|--------------|
| Phase 1: Standalone Conversion | 1 day | None | Search converted to TS |
| Phase 2: Core Utilities | 1-2 days | None | Storage, DOM, API, events modules |
-| Phase 3: Low Complexity | 3-4 days | Phase 2 | Search, header, admin in TS |
-| Phase 4: Medium Complexity | 4-5 days | Phase 2, 3 | Collections, bookshelf, devices in TS |
-| Phase 5: High Complexity | 5-6 days | Phase 4 | Linking, API explorer in TS |
-| Phase 6: Integration & Cleanup | 2-3 days | All phases | Clean templates, verified functionality |
-| **Total** | **16-21 days** | | **Full TypeScript conversion** |
+| Phase 3: Low Complexity | 2-3 days | Phase 2 | Search, header, admin, analytics in TS |
+| Phase 4: Medium Complexity | 3-4 days | Phase 2, 3 | Collections, bookshelf, devices, queue in TS |
+| Phase 5: High Complexity | 4-5 days | Phase 4 | Linking, API explorer, conflicts in TS |
+| Phase 6: Integration & Cleanup | 2-3 days | All phases | Clean templates, docs search, verified functionality |
+| **Total** | **13-18 days** | | **Full TypeScript conversion** |
+
+**Note:** Timeline reduced from initial estimate based on actual line count verification (~2,800 lines vs original ~6,300 estimate). Three templates (conflicts, queue, analytics) have NO inline script blocks and only need TypeScript modules for their onclick handlers.
---
## Success Criteria
-- [ ] All inline JavaScript removed from templates
+- [ ] All inline JavaScript logic removed from templates (functions, fetch calls, state management)
- [ ] All features work with TypeScript
- [ ] TypeScript compilation passes with strict mode
-- [ ] Progressive enhancement maintained (pages work without JS)
+- [ ] No regression in existing progressive enhancement behavior (HTMX forms unchanged)
- [ ] No type `any` (except for legacy API responses)
-- [ ] Event delegation replaces inline handlers
-- [ ] Build process integrated into npm scripts
+- [ ] Event handlers use pragmatic mix of onclick, data-action, and form interception as appropriate
+- [ ] **Type definitions use snake_case field names matching actual API responses**
+- [ ] **Type definitions verified against endpoint returns (not just handler structs)**
+- [ ] Type definition comments reference source files:
+ - Database rows: `// Matches internal/database/queries.sql.go:SearchMediaItemsRow`
+ - Handler structs: `// Matches handlers.BookInfo JSON: media_item_id, title, author`
+- [ ] `import type { ... } from './types/api'` pattern used for type checking
- [ ] Full manual testing completed
- [ ] No regression in functionality
- [ ] Bundle sizes monitored and optimized
- [ ] SSR still works for all initial page loads
- [ ] All CRUD operations use existing `/api/*` endpoints
+- [ ] Login template duplicate theme logic removed (lines 62-88)
+- [ ] Search functionality uses correct types matching database SearchMediaItemsRow
+- [ ] Analytics page loads data and displays charts correctly
+- [ ] Queue management CRUD operations work (process, clear, filter)
+- [ ] Conflict resolution works (individual and bulk operations)
+- [ ] Documentation search functionality works
+- [ ] All 10 missing type definitions added (analytics, queue, conflicts)
+- [ ] Total converted: ~2,800 lines of inline script blocks
---
## Next Steps
1. **Review and approve** this plan
-2. **Setup Phase 1**: Convert `search.js` → TypeScript
-3. **Begin Phase 2**: Create core utility modules
+2. **Setup Phase 1**: Convert `search.js` → TypeScript (ensure snake_case field names)
+3. **Begin Phase 2**: Create core utility modules (including `types/api.d.ts` with snake_case types)
4. **Iterate** through phases 3-5 (feature conversion)
-5. **Phase 6**: Final integration and cleanup
+5. **Phase 6**: Final integration and cleanup (remove login.templ duplicate theme code, lines 62-88)
+
+---
+
+## Appendix: Common Pitfalls
+
+### Database Rows vs Handler Structs
+
+**Problem:** Some handler files define structs that don't match what the endpoint actually returns.
+
+**Example:**
+```go
+// internal/handlers/search.go
+// This struct is DEFINED but never used!
+type MediaItemSummary struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Authors []SearchAuthor `json:"authors"` // Wrong! API returns string, not array
+}
+
+// internal/handlers/media.go
+// The endpoint actually returns database rows:
+func (mh *MediaHandler) SearchMediaItems(c echo.Context) error {
+ partialResults, err := mh.db.SearchMediaItems(...) // Returns []SearchMediaItemsRow
+ return c.JSON(http.StatusOK, partialResults) // Has library_id, author (string), etc.
+}
+```
+
+**Solution:**
+1. Grep the endpoint function to find what it actually returns
+2. Check the return statement: `return c.JSON(..., variable)`
+3. Find that variable's type definition
+4. Use that type's JSON tags, not some unrelated handler struct
+
+**Verification:**
+```bash
+# Find endpoint implementation
+rg "func.*SearchMediaItems" internal/handlers/
+# Check what it returns
+rg -A 5 "return c.JSON" internal/handlers/media.go
+# Find the returned type
+rg "type SearchMediaItemsRow" internal/database/
+```
+
+---
+
+## Appendix: Progressive Enhancement Test Checklist
+
+Use this checklist when converting templates to ensure progressive enhancement is maintained:
+
+**Before removing inline JavaScript:**
+- [ ] Identify all forms in the template
+- [ ] Verify each form has `action` and `method` attributes
+- [ ] Verify each form has a submit button (not just button with onclick)
+- [ ] Test form submission works with JavaScript disabled
+- [ ] Identify all critical user flows (login, create, update, delete)
+- [ ] Verify each critical flow has a non-JavaScript fallback
+
+**After TypeScript conversion:**
+- [ ] Test with JavaScript disabled (DevTools → Disable JavaScript)
+- [ ] Verify forms still submit with full page reload
+- [ ] Verify navigation links work (full page reload)
+- [ ] Verify all data is visible on initial load (SSR)
+- [ ] Test critical user flows work without JavaScript
+- [ ] Enable JavaScript and verify enhanced experience
+
+**What to test without JavaScript:**
+- [ ] Page loads and displays data
+- [ ] Forms can be submitted
+- [ ] Navigation links work
+- [ ] No console errors
+
+**What requires JavaScript (expected to fail without JS):**
+- [ ] Toast notifications
+- [ ] AJAX form submission (no page reload)
+- [ ] Modal dialogs
+- [ ] Dynamic content updates
+- [ ] Keyboard navigation shortcuts
+- [ ] Auto-complete search
---
@@ -857,13 +1509,11 @@ git checkout backup-before-collections -- templates/collections.templ
```
-### After: TypeScript Module + Event Delegation
+### After: TypeScript Module (Browser Globals)
**TypeScript Module:**
```typescript
-// web/ts/features/collections/rules.ts
-import { apiClient } from '../../core/api.js';
-import { escapeHtml } from '../../core/dom.js';
+// web/src/collections.ts
interface Rule {
id: string;
@@ -872,21 +1522,15 @@ interface Rule {
value: string;
}
-function renderRules(rules: Rule[]): void {
- const container = document.getElementById('rules-container');
- if (!container) return;
-
- container.innerHTML = rules.map(rule => `
-
- ${escapeHtml(rule.field)}
-
-
- `).join('');
+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 apiClient.get(`/collections/${collectionId}/rules`);
+ const response = await (window as any).api.get(`/collections/${collectionId}/rules`);
if (!response.ok) {
throw new Error('Failed to load rules');
@@ -896,7 +1540,35 @@ async function loadRules(collectionId: string): Promise {
renderRules(rules);
} catch (error) {
console.error('Load rules error:', error);
- window.showToast.error('Failed to load rules');
+ (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');
}
}
@@ -906,45 +1578,41 @@ async function deleteRule(collectionId: string, ruleId: string): Promise {
}
try {
- const response = await apiClient.delete(`/collections/${collectionId}/rules/${ruleId}`);
+ const response = await (window as any).api.delete(`/collections/${collectionId}/rules/${ruleId}`);
if (!response.ok) {
throw new Error('Failed to delete rule');
}
- window.showToast.success('Rule deleted');
+ (window as any).showToast.success('Rule deleted');
- // Remove from DOM
const ruleElement = document.querySelector(`[data-rule-id="${ruleId}"]`);
ruleElement?.remove();
} catch (error) {
console.error('Delete rule error:', error);
- window.showToast.error('Failed to delete rule');
+ (window as any).showToast.error('Failed to delete rule');
}
}
-// Event delegation for delete buttons
-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;
- const collectionId = deleteBtn.dataset.collectionId;
-
- if (ruleId && collectionId) {
- deleteRule(collectionId, ruleId);
- }
- }
-});
+// Export to window for HTML access
+(window as any).loadRules = loadRules;
+(window as any).deleteRule = deleteRule;
// Initialize on page load
-document.addEventListener('DOMContentLoaded', () => {
+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:**
@@ -954,9 +1622,9 @@ document.addEventListener('DOMContentLoaded', () => {
-
-
-
+
+
+
@@ -964,9 +1632,7 @@ document.addEventListener('DOMContentLoaded', () => {
{ range .Rules }
{ .Field }
-
@@ -977,16 +1643,24 @@ document.addEventListener('DOMContentLoaded', () => {
```
**Benefits:**
-- ✅ No inline JavaScript
-- ✅ Event delegation (one listener)
-- ✅ Type-safe API calls
-- ✅ Better error handling
-- ✅ Reusable module
+- ✅ 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*
+*Architecture: Hybrid SSR + TypeScript CRUD (Browser Globals, No ES Modules)*