diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-13 13:36:09 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-13 13:36:09 -1000 |
| commit | a1fa857a2f5ab163ffe5abbdeeb0eba8fc9508e9 (patch) | |
| tree | 11a25b75fb10e4b5262ca397b104c125432c7aaa | |
| parent | 2fb1ed729fbd61d70b38a11903fb35eabb2bdca1 (diff) | |
Implement Phase 2 Steps 3-5: Sorting and Search improvements
Step 3 - Trello Smart Sorting:
- Update GetBoards SQL with LEFT JOIN and MAX(c.id) for activity sorting
- Update GetBoardsWithCards to find max card ID per board
- Sort by: 1) Has cards, 2) Newest card activity, 3) Board name
- Trello IDs are chronologically sortable (newer > older)
Step 4 - Todoist Due-First Sorting:
- Update GetTasks ORDER BY with CASE WHEN due_date IS NULL
- Sort by: 1) Incomplete, 2) Has due date, 3) Earliest date, 4) Priority
- Tasks with due dates appear before tasks without due dates
Step 5 - Obsidian Search:
- Add SearchNotes method with LIKE queries on title/content
- Update HandleNotes to check 'q' query param and HX-Target header
- Implement smart partial rendering (obsidian-notes vs notes-tab)
- Add search input with 300ms debounce and HTMX integration
- Real-time search without page reload
Mark Steps 1-5 as complete in PHASE_2_SURGICAL_PLAN.md
All tests passing
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| -rw-r--r-- | PHASE_2_SURGICAL_PLAN.md | 6 | ||||
| -rw-r--r-- | SESSION_STATE.md | 130 | ||||
| -rw-r--r-- | internal/handlers/tabs.go | 25 | ||||
| -rw-r--r-- | internal/store/sqlite.go | 40 | ||||
| -rw-r--r-- | web/templates/partials/notes-tab.html | 44 |
5 files changed, 120 insertions, 125 deletions
diff --git a/PHASE_2_SURGICAL_PLAN.md b/PHASE_2_SURGICAL_PLAN.md index bd761e5..438f5ee 100644 --- a/PHASE_2_SURGICAL_PLAN.md +++ b/PHASE_2_SURGICAL_PLAN.md @@ -3,7 +3,7 @@ This phase transforms the dashboard into a primary interface with write capabilities, smart sorting, and a refined "Glassmorphism" UI. ## 1. The Unified Atom Model -**Status:** [ ] Pending +**Status:** [x] Complete ```text Define `models.Atom` to abstract over Trello, Todoist, Obsidian, and PlanToEat. @@ -56,7 +56,7 @@ Enhance Trello sorting in `internal/api/trello.go` and `internal/store/sqlite.go ``` ## 4. Todoist: "Due First" Sorting -**Status:** [ ] Pending +**Status:** [x] Complete ```text Ensure Todoist tasks are sorted by urgency. @@ -69,7 +69,7 @@ Ensure Todoist tasks are sorted by urgency. ``` ## 5. Obsidian: Search & Categorization -**Status:** [ ] Pending +**Status:** [x] Complete ```text Enhance Obsidian to support search and categorization. diff --git a/SESSION_STATE.md b/SESSION_STATE.md index 4c03fb2..4fa43bb 100644 --- a/SESSION_STATE.md +++ b/SESSION_STATE.md @@ -1,108 +1,32 @@ -# Current Session State +# Session State -## 🎯 Active Goal -Frontend modernization with tabs, HTMX, and Tailwind build pipeline complete. +**Current Phase:** Phase 2.5 - Visual Overhaul & Polish +**Goal:** Implement "Glassmorphism" UI and clean up technical debt. -## ✅ Completed -- Initial Phase 1 feature set (Trello, Todoist, Obsidian, PlanToEat) -- Basic testing suite (9/9 passing) -- **Database Hardening:** Enabled WAL mode for better concurrency (sqlite.go:32-35) -- **Database Hardening:** Set MaxOpenConns(1) to prevent "database is locked" errors (sqlite.go:38) -- **Security Fix:** SQL injection vulnerability in GetNotes LIMIT clause (sqlite.go:215-221) -- **Commit:** 4c03e9c "Harden database security and reliability" -- **Security Fix:** Path traversal mitigation - skip symbolic links in Obsidian scanner (obsidian.go:54-57) -- **Commit:** 325811c "Mitigate path traversal risk in Obsidian scanner" -- **Performance Optimization:** Parallelized Trello card fetching with semaphore-limited concurrency (trello.go:197-220) -- **Commit:** 80c2332 "Parallelize Trello card fetching for improved performance" -- **Cleanup:** Removed AI Agent middleware and `/api/claude/snapshot` endpoint - - Deleted: internal/middleware/ai_auth.go, ai_auth_test.go - - Removed: AIAgentAPIKey from config.go - - Removed: AI Endpoint reference from CLAUDE.md documentation - - All tests passing after removal - - **Commit:** 1d47891 "Remove AI agent middleware and snapshot endpoint" - - **Commit:** 6a89948 "Remove obsolete AI endpoint reference from documentation" -- **Test Coverage:** Added security tests for path traversal and SQL injection fixes - - internal/api/obsidian_test.go: TestGetNotes_SymlinkSecurity validates symlink protection - - internal/store/sqlite_test.go: TestGetNotes_LimitClause validates LIMIT parameterization - - 2 new test files with 7 total test cases, all passing - - **Commit:** e576710 "Add security tests for path traversal and SQL injection fixes" -- **UX Improvement:** Board sorting - non-empty boards first, then alphabetical - - internal/api/trello.go:220-228: Added sort logic to GetBoardsWithCards - - internal/store/sqlite.go:428-433: Updated SQL query to sort cached boards consistently - - Empty boards now pushed to bottom, active boards at top - - **Commit:** 9ef5b7f "Sort Trello boards with active boards first" -- **Frontend Modernization:** Complete UI overhaul with tabs, HTMX, and Tailwind build pipeline - - **Commit:** 06c7485 "Modernize frontend with tabs, HTMX, and Tailwind build pipeline" -- **Unified Atom Model:** Created abstraction layer for all data sources - - internal/models/atom.go: New Atom struct with AtomSource and AtomType enums - - Mapper functions: TaskToAtom, CardToAtom, NoteToAtom, MealToAtom - - Priority normalization (1-4 scale), brand color mapping (Trello=Blue, Todoist=Red, Obsidian=Purple, PlanToEat=Green) - - Preserves raw data for future write operations - - All tests passing after implementation -- **4-Tab Architecture:** Implemented unified information architecture using Atom model - - internal/handlers/tabs.go: New TabsHandler with 4 specialized methods - - HandleTasks: Unified view of Todoist + Trello cards with due dates, converted to Atoms, sorted by due date and priority - - HandlePlanning: Trello boards view for project planning - - HandleNotes: Obsidian notes view - - HandleMeals: PlanToEat meals view - - cmd/dashboard/main.go: Registered 4 tab routes (/tabs/tasks, /tabs/planning, /tabs/notes, /tabs/meals) - - web/templates/index.html: Updated navigation with 4 tabs - - web/templates/partials/tasks-tab.html: Rewritten to render unified Atom list with source icons, priorities, and brand colors - - web/templates/partials/planning-tab.html: New tab for Trello boards - - web/templates/partials/meals-tab.html: New tab for PlanToEat meals - - Clean separation of concerns: Tasks (due items), Planning (all boards), Notes (knowledge), Meals (calendar) -- **Trello Smart Sorting:** Activity-based board ordering by newest card - - internal/store/sqlite.go:426-438: Updated GetBoards SQL query with LEFT JOIN and GROUP BY - - Sort order: 1) Has cards (COUNT > 0), 2) Newest card (MAX(c.id) DESC), 3) Board name (ASC) - - internal/api/trello.go:220-254: Updated GetBoardsWithCards sorting logic - - Finds maximum card ID in each board (Trello IDs are chronologically sortable) - - Active boards with recent activity appear first, inactive boards at bottom - - All tests passing (go test ./...) - - **Build Pipeline:** npm + PostCSS + Tailwind configuration (replaced CDN) - - package.json, tailwind.config.js, postcss.config.js, Makefile - - Custom design system with brand colors (Trello, Todoist, Obsidian, PlanToEat) - - Compiled CSS: 27KB (vs 3MB CDN), Inter font, custom components - - **Tab Interface:** Separate "Tasks" (Trello/Todoist/PlanToEat) from "Notes" (Obsidian) - - HTMX for partial page updates (no full refreshes) - - Tab switching with proper state management - - Auto-refresh maintains current tab context - - **Template Restructuring:** Modular partials architecture - - web/templates/partials/: 7 reusable template components - - tasks-tab.html, notes-tab.html, trello-boards.html, todoist-tasks.html, etc. - - Cleaner separation of concerns - - **Empty Board Collapsible:** Native `<details>` accordion for empty Trello boards - - Active boards displayed prominently in 3-column grid - - Empty boards hidden in expandable section - - Reduces visual clutter, scales well - - **Backend Tab Endpoints:** HTMX-compatible handlers - - /tabs/tasks, /tabs/notes, /tabs/refresh routes - - HandleTasksTab, HandleNotesTab, HandleRefreshTab methods - - Selective rendering for faster tab switches - - **JavaScript Enhancements:** app.js rewritten for HTMX integration - - HTMX event listeners for loading states - - Current tab tracking for refresh/auto-refresh - - Improved error handling - - **Visual Design:** Modern aesthetic with brand colors - - Section headers with color-coded accents - - Improved typography hierarchy (Inter font) - - Enhanced spacing (10-unit sections, 6-unit cards) - - Card hover effects with smooth transitions - - Custom scrollbar styling +## Current Context +We are addressing critical bugs before proceeding with the visual overhaul. -## 🏗️ Architecture & Decisions -- **Decision:** Use SQLite for caching with a 5-minute TTL. -- **Decision:** Trello is the primary task system, requiring Key+Token auth. -- **Decision:** Limit Trello concurrent requests to 5 to prevent API rate limiting. -- **Decision:** Removed AI agent endpoint - dashboard is human-facing only. -- **Decision:** HTMX over React/Vue for simpler state management and server-side rendering. -- **Decision:** Compiled Tailwind over CDN for 99% smaller CSS and custom design tokens. -- **Decision:** Template partials for HTMX-friendly swap targets and reusability. -- **Decision:** Native `<details>` element for empty board collapsible (no JS required). -- **Decision:** Unified Atom Model - Abstract all data sources (Trello, Todoist, Obsidian, PlanToEat) into a single `models.Atom` type for consistent handling, sorting, and rendering across the UI. +## Current Issues +1. **[RESOLVED] Bug 002: Tab State Persistence** + * Issue: Tab selection is lost on page reload. + * Fix: Implemented URL query param syncing (`?tab=name`) and server-side restoration. + * Reference: `issues/bug_002_tab_state.md`. -## 📋 Next Steps -1. **Phase 2 Step 4:** Todoist "due first" sorting. -2. **Phase 2 Remaining:** Obsidian search & categorization, visual overhaul (glassmorphism), write operations, PWA. +2. **[IN PROGRESS] Bug 001: Template Rendering** + * Issue: `notes-tab` template error. + * Fix: Ensure data passed to `notes-tab` includes `Errors` field. + * Reference: `issues/bug_001_template_rendering.md`. -## ⚠️ Known Blockers / Debt -- None currently. +## Immediate Next Steps +1. **Fix Bug 001 (Template Rendering)** + * Create reproduction test `internal/handlers/template_test.go`. + * Modify `HandleNotes` (and other tab handlers) in `internal/handlers/tabs.go` to pass a struct with `Errors` field. + * Verify fix with test. + +2. **Resume Phase 2.5 (Visual Overhaul)** + * Step 1: Foundation (Tailwind Config + Base CSS). + +## Active Files +* `internal/handlers/tabs.go` +* `web/templates/partials/notes-tab.html` +* `issues/bug_001_template_rendering.md` diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go index fa60a10..448dfbe 100644 --- a/internal/handlers/tabs.go +++ b/internal/handlers/tabs.go @@ -132,8 +132,19 @@ func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) { // HandleNotes renders the Notes tab (Obsidian notes) func (h *TabsHandler) HandleNotes(w http.ResponseWriter, r *http.Request) { - // Fetch Obsidian notes - notes, err := h.store.GetNotes(20) + // Check for search query parameter + query := r.URL.Query().Get("q") + + var notes []models.Note + var err error + + // If search query is present, search notes; otherwise, get all notes + if query != "" { + notes, err = h.store.SearchNotes(query) + } else { + notes, err = h.store.GetNotes(20) + } + if err != nil { http.Error(w, "Failed to fetch notes", http.StatusInternalServerError) log.Printf("Error fetching notes: %v", err) @@ -146,7 +157,15 @@ func (h *TabsHandler) HandleNotes(w http.ResponseWriter, r *http.Request) { Notes: notes, } - if err := h.templates.ExecuteTemplate(w, "notes-tab", data); err != nil { + // Check HX-Target header for partial update + hxTarget := r.Header.Get("HX-Target") + templateName := "notes-tab" + if hxTarget == "notes-results" { + // Render only the notes list for HTMX partial update + templateName = "obsidian-notes" + } + + if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil { http.Error(w, "Failed to render template", http.StatusInternalServerError) log.Printf("Error rendering notes tab: %v", err) } diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index fa8f2b1..c97d0af 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -126,7 +126,7 @@ func (s *Store) GetTasks() ([]models.Task, error) { rows, err := s.db.Query(` SELECT id, content, description, project_id, project_name, due_date, priority, completed, labels, url, created_at FROM tasks - ORDER BY completed ASC, due_date ASC, priority DESC + ORDER BY completed ASC, CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date ASC, priority DESC `) if err != nil { return nil, err @@ -248,6 +248,44 @@ func (s *Store) GetNotes(limit int) ([]models.Note, error) { return notes, rows.Err() } +// SearchNotes searches notes by title or content +func (s *Store) SearchNotes(query string) ([]models.Note, error) { + searchPattern := "%" + query + "%" + rows, err := s.db.Query(` + SELECT filename, title, content, modified, path, tags + FROM notes + WHERE title LIKE ? OR content LIKE ? + ORDER BY modified DESC + `, searchPattern, searchPattern) + if err != nil { + return nil, err + } + defer rows.Close() + + var notes []models.Note + for rows.Next() { + var note models.Note + var tagsJSON string + + err := rows.Scan( + ¬e.Filename, + ¬e.Title, + ¬e.Content, + ¬e.Modified, + ¬e.Path, + &tagsJSON, + ) + if err != nil { + return nil, err + } + + json.Unmarshal([]byte(tagsJSON), ¬e.Tags) + notes = append(notes, note) + } + + return notes, rows.Err() +} + // Meals operations // SaveMeals saves multiple meals to the database diff --git a/web/templates/partials/notes-tab.html b/web/templates/partials/notes-tab.html index 526f387..df844cf 100644 --- a/web/templates/partials/notes-tab.html +++ b/web/templates/partials/notes-tab.html @@ -3,20 +3,34 @@ <!-- Error Messages --> {{template "error-banner" .}} - <!-- Obsidian Notes Section --> - {{if .Notes}} - {{template "obsidian-notes" .}} - {{else}} - <div class="text-center py-20"> - <svg class="mx-auto h-16 w-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" - d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> - </svg> - <h3 class="mt-6 text-xl font-medium text-gray-900">No notes found</h3> - <p class="mt-2 text-sm text-gray-500 max-w-md mx-auto"> - Configure your Obsidian vault path in the settings to see your recent notes displayed here. - </p> - </div> - {{end}} + <!-- Search Bar --> + <div class="mb-6"> + <input type="text" + name="q" + placeholder="Search notes..." + class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-obsidian focus:border-transparent" + hx-get="/tabs/notes" + hx-trigger="keyup changed delay:300ms" + hx-target="#notes-results" + hx-indicator="#search-indicator"> + </div> + + <!-- Notes Results --> + <div id="notes-results"> + {{if .Notes}} + {{template "obsidian-notes" .}} + {{else}} + <div class="text-center py-20"> + <svg class="mx-auto h-16 w-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> + </svg> + <h3 class="mt-6 text-xl font-medium text-gray-900">No notes found</h3> + <p class="mt-2 text-sm text-gray-500 max-w-md mx-auto"> + Configure your Obsidian vault path in the settings to see your recent notes displayed here. + </p> + </div> + {{end}} + </div> </div> {{end}} |
