summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-13 13:36:09 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-13 13:36:09 -1000
commita1fa857a2f5ab163ffe5abbdeeb0eba8fc9508e9 (patch)
tree11a25b75fb10e4b5262ca397b104c125432c7aaa
parent2fb1ed729fbd61d70b38a11903fb35eabb2bdca1 (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.md6
-rw-r--r--SESSION_STATE.md130
-rw-r--r--internal/handlers/tabs.go25
-rw-r--r--internal/store/sqlite.go40
-rw-r--r--web/templates/partials/notes-tab.html44
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(
+ &note.Filename,
+ &note.Title,
+ &note.Content,
+ &note.Modified,
+ &note.Path,
+ &tagsJSON,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ json.Unmarshal([]byte(tagsJSON), &note.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}}