summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-22 10:09:07 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-22 10:09:07 -1000
commit7fd381a242f68b7c6f10db4e3ae0bb3d06e36a16 (patch)
treeabff0af0a0f3b057d7b1ad6d95dbefdf30c553c3
parent583f90c5dedf0235fa45557359b0e6e7dd62b0f0 (diff)
Fix background image CORS issue
Switch from Unsplash Source API to Lorem Picsum which has proper CORS headers for cross-origin image loading. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
-rwxr-xr-xappbin0 -> 18004488 bytes
-rw-r--r--internal/handlers/handlers.go6
-rw-r--r--issues/001-hide-future-tasks-behind-fold.md51
-rw-r--r--issues/002-modal-menu-quick-add-bug-report.md47
-rw-r--r--issues/003-fix-tap-to-expand.md48
-rw-r--r--issues/004-integrate-terst-org.md62
-rw-r--r--issues/005-visual-task-timing-differentiation.md61
-rw-r--r--issues/006-reorder-tasks-by-urgency.md63
-rw-r--r--issues/007-fix-outdated-todoist-link.md54
-rw-r--r--issues/008-add-google-calendar-support.md85
-rw-r--r--issues/009-keep-completed-tasks-visible.md54
-rw-r--r--issues/010-fix-quick-add-timestamp.md76
-rw-r--r--issues/011-add-timeline-view.md86
-rw-r--r--issues/013-quick-add-shopping-list.md81
-rw-r--r--issues/015-random-landscape-background.md77
-rw-r--r--issues/016-click-task-edit-details.md80
16 files changed, 928 insertions, 3 deletions
diff --git a/app b/app
new file mode 100755
index 0000000..6ffa2aa
--- /dev/null
+++ b/app
Binary files differ
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 73a05f0..19415c7 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -78,9 +78,9 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) {
return
}
- // Generate random background URL (Unsplash Source API)
- // Add timestamp to prevent caching
- backgroundURL := fmt.Sprintf("https://source.unsplash.com/1920x1080/?landscape,nature&t=%d", time.Now().UnixNano())
+ // Generate random background URL (Lorem Picsum - CORS-friendly)
+ // Add random seed to get different image each load
+ backgroundURL := fmt.Sprintf("https://picsum.photos/1920/1080?random=%d", time.Now().UnixNano())
// Wrap dashboard data with active tab for template
data := struct {
diff --git a/issues/001-hide-future-tasks-behind-fold.md b/issues/001-hide-future-tasks-behind-fold.md
new file mode 100644
index 0000000..f6012fe
--- /dev/null
+++ b/issues/001-hide-future-tasks-behind-fold.md
@@ -0,0 +1,51 @@
+# [FEATURE] Hide future tasks behind a fold
+
+## Description
+Hide tasks more than 3-7 days out behind a fold.
+
+## User Story
+As a user, I want tasks more than N days out hidden by default so that I can focus on immediate priorities.
+
+## Technical Context
+- Affects: `internal/handlers/handlers.go` (task filtering), `web/templates/partials/todoist-tasks.html` or `tasks-tab.html`
+- Data already has `due_date`; filtering is a presentation concern
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+Test a new filter function `FilterTasksByHorizon(tasks []Task, days int)` returns only tasks within range.
+
+```go
+func TestFilterTasksByHorizon(t *testing.T) {
+ // Given tasks with varying due dates
+ // When filtered with horizon of 7 days
+ // Then only tasks within 7 days are in "visible" slice
+ // And remaining tasks are in "deferred" slice
+}
+```
+
+### E2E Test (Red)
+Verify collapsed section renders with count badge; click expands to show deferred tasks.
+
+## Proposed Approach
+
+1. Add config param `task_horizon_days` (default 7)
+2. Partition tasks in handler into "visible" vs "deferred"
+3. Render deferred in a collapsible `<details>` element with HTMX swap
+4. Show count badge on collapsed header (e.g., "+12 later")
+
+## Affected Components
+- `internal/handlers/handlers.go`
+- `internal/config/config.go`
+- `web/templates/partials/todoist-tasks.html`
+- `web/templates/partials/tasks-tab.html`
+
+## Definition of Done
+- [ ] Tasks beyond horizon hidden by default
+- [ ] Collapsible UI shows count of hidden tasks
+- [ ] Click to expand shows all deferred tasks
+- [ ] Horizon days configurable
+- [ ] Unit tests pass
+- [ ] E2E test confirms behavior
diff --git a/issues/002-modal-menu-quick-add-bug-report.md b/issues/002-modal-menu-quick-add-bug-report.md
new file mode 100644
index 0000000..b518cf3
--- /dev/null
+++ b/issues/002-modal-menu-quick-add-bug-report.md
@@ -0,0 +1,47 @@
+# [FEATURE] Quick add & bug report in modal menu
+
+## Description
+Put quick add (rename "add" to "quick add") and bug report in a unified modal menu.
+
+## User Story
+As a user, I want quick-add and bug-report behind a unified modal so the UI is cleaner.
+
+## Technical Context
+- Affects: `web/templates/index.html` (modal container), new partial `partials/modal-menu.html`
+- Existing handlers for quick-add and bug already exist; this is UI consolidation
+- Note: Issue #14 is a duplicate of this
+
+## Test Strategy
+
+### E2E/Integration Test (Red)
+Test modal opens on trigger, contains both forms, submits correctly.
+
+```
+1. Click menu trigger -> modal opens
+2. Assert "Add Task" and "Report Bug" tabs/options visible
+3. Fill quick-add form -> submit -> task created
+4. Switch to bug tab -> fill -> submit -> bug created
+5. Modal closes after successful submit
+```
+
+## Proposed Approach
+
+1. Create shared modal partial with tab/toggle between "Add Task" and "Report Bug"
+2. Use HTMX `hx-get` to lazy-load modal content
+3. Add keyboard shortcut (e.g., `Cmd+K` or `Ctrl+K`) to invoke
+4. Rename "Add" button to "Quick Add" throughout UI
+5. Single FAB or menu button replaces separate add/bug buttons
+
+## Affected Components
+- `web/templates/index.html`
+- `web/templates/partials/modal-menu.html` (new)
+- `web/static/` (JS for keyboard shortcut)
+- Existing quick-add and bug handlers (no changes, just UI consolidation)
+
+## Definition of Done
+- [ ] Single modal contains both forms
+- [ ] Tab/toggle switches between quick-add and bug report
+- [ ] Keyboard shortcut opens modal
+- [ ] Both forms submit correctly
+- [ ] Modal closes on successful submit
+- [ ] Old separate UI elements removed
diff --git a/issues/003-fix-tap-to-expand.md b/issues/003-fix-tap-to-expand.md
new file mode 100644
index 0000000..a1cfd8c
--- /dev/null
+++ b/issues/003-fix-tap-to-expand.md
@@ -0,0 +1,48 @@
+# [BUG] Tap to expand doesn't work
+
+## Description
+Tap to expand doesn't work on task items.
+
+## Technical Context
+- Likely a JS/HTMX event binding issue in task list items
+- Affects: `partials/todoist-tasks.html` or `tasks-tab.html`, possibly JS in `web/static/`
+- May be related to event delegation on dynamically-loaded content
+
+## Test Strategy
+
+### E2E Test (Red)
+Playwright/browser test: click task row -> assert expanded content visible.
+
+```
+1. Load tasks page
+2. Click on a task row
+3. Assert expanded details section becomes visible
+4. Assert expanded content contains expected task details
+```
+
+## Proposed Approach
+
+1. Inspect current expand trigger mechanism:
+ - Is it `hx-get` with `hx-trigger="click"`?
+ - Is it a JS click handler?
+ - Is it a `<details>` element?
+2. Check for missing `hx-trigger` or event delegation issues on dynamically-loaded content
+3. Ensure `htmx:afterSwap` rebinds event listeners if needed
+4. Test on both desktop (click) and mobile (tap)
+
+## Debugging Steps
+1. Open browser dev tools -> check for JS errors on click
+2. Inspect element -> verify HTMX attributes present
+3. Check Network tab -> see if request fires on click
+4. If using JS handlers, verify they're attached after HTMX swaps
+
+## Affected Components
+- `web/templates/partials/todoist-tasks.html`
+- `web/templates/partials/tasks-tab.html`
+- `web/static/` (if JS-based)
+
+## Definition of Done
+- [ ] Clicking/tapping a task expands its details
+- [ ] Works on desktop and mobile
+- [ ] Works on initial load and after HTMX swaps
+- [ ] E2E test confirms behavior
diff --git a/issues/004-integrate-terst-org.md b/issues/004-integrate-terst-org.md
new file mode 100644
index 0000000..9ec79bf
--- /dev/null
+++ b/issues/004-integrate-terst-org.md
@@ -0,0 +1,62 @@
+# [FEATURE] Integrate terst.org start page and styling
+
+## Description
+Integrate terst.org start page widgets and visual styling.
+
+## User Story
+As a user, I want the dashboard to incorporate my terst.org start page widgets and match its visual styling for a unified experience.
+
+## Technical Context
+- Two aspects: visual styling + widget integration
+- Requires understanding terst.org's current design system and widget architecture
+- May need API access or scraping depending on terst.org implementation
+
+## Test Strategy
+
+### E2E Test (Red)
+```
+1. Load dashboard
+2. Assert color scheme matches terst.org (primary colors, fonts)
+3. Assert expected widgets render (TBD based on widget inventory)
+```
+
+## Proposed Approach
+
+### Phase 1: Visual Styling
+1. **Audit terst.org design:**
+ - Extract color palette (primary, secondary, accent)
+ - Identify fonts and typography scale
+ - Note spacing/layout patterns
+2. **Update Tailwind config:**
+ - Add terst.org colors to theme
+ - Update default styles to match
+3. **Apply to existing components**
+
+### Phase 2: Widget Integration
+1. **Inventory terst.org widgets:**
+ - What widgets exist? (clock, weather, links, etc.)
+ - How are they implemented? (static HTML, JS, API-backed?)
+2. **Integration options:**
+ - Option A: Embed via iframe (quick but limited)
+ - Option B: Recreate widgets natively (more work, better integration)
+ - Option C: API calls to terst.org backend (if available)
+3. **Implement priority widgets first**
+
+## Discovery Required
+- [ ] Access terst.org to inventory widgets
+- [ ] Extract color palette and design tokens
+- [ ] Determine widget data sources
+
+## Affected Components
+- `tailwind.config.js` (theme colors)
+- `web/static/css/` (global styles)
+- `web/templates/index.html` (widget containers)
+- `web/templates/partials/` (new widget partials)
+- Potentially `internal/handlers/` (widget data endpoints)
+
+## Definition of Done
+- [ ] Color scheme matches terst.org
+- [ ] Typography matches terst.org
+- [ ] Priority widgets integrated
+- [ ] Responsive layout preserved
+- [ ] E2E test verifies styling
diff --git a/issues/005-visual-task-timing-differentiation.md b/issues/005-visual-task-timing-differentiation.md
new file mode 100644
index 0000000..4cc40b4
--- /dev/null
+++ b/issues/005-visual-task-timing-differentiation.md
@@ -0,0 +1,61 @@
+# [FEATURE] Visual differentiation for task timing
+
+## Description
+Tasks after today should be grayed out, past deadline should be emphasized.
+
+## User Story
+As a user, I want past-due tasks highlighted and future tasks dimmed so urgency is immediately visible.
+
+## Technical Context
+- Pure presentation logic in template
+- Affects: `partials/todoist-tasks.html`, `partials/tasks-tab.html`
+- Requires comparing task `due_date` against current date
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+Test helper function returns correct urgency class.
+
+```go
+func TestTaskUrgencyClass(t *testing.T) {
+ tests := []struct {
+ name string
+ dueDate time.Time
+ expected string
+ }{
+ {"overdue yesterday", yesterday, "overdue"},
+ {"due today", today, "today"},
+ {"due tomorrow", tomorrow, "future"},
+ {"no due date", zeroTime, "none"},
+ }
+ // ...
+}
+```
+
+### E2E Test (Red)
+Assert CSS classes applied correctly to tasks based on due date.
+
+## Proposed Approach
+
+1. Add template helper or compute urgency class in handler
+2. Return struct with `UrgencyClass` field for each task
+3. Apply Tailwind classes based on urgency:
+ - `text-red-600 font-bold` (overdue)
+ - `text-inherit` or default (today)
+ - `text-gray-400` (future)
+4. Consider adding visual indicator icon (e.g., warning for overdue)
+
+## Affected Components
+- `internal/handlers/handlers.go`
+- `internal/models/types.go` (if adding field to view model)
+- `web/templates/partials/todoist-tasks.html`
+- `web/templates/partials/tasks-tab.html`
+
+## Definition of Done
+- [ ] Overdue tasks visually emphasized (red/bold)
+- [ ] Future tasks grayed out
+- [ ] Today's tasks normal styling
+- [ ] Unit tests for urgency calculation
+- [ ] E2E test verifies correct classes applied
diff --git a/issues/006-reorder-tasks-by-urgency.md b/issues/006-reorder-tasks-by-urgency.md
new file mode 100644
index 0000000..8a5f968
--- /dev/null
+++ b/issues/006-reorder-tasks-by-urgency.md
@@ -0,0 +1,63 @@
+# [FEATURE] Reorder task list by urgency
+
+## Description
+List past due tasks, then tasks with a specific time today, then 12am (all-day) tasks today, then future tasks.
+
+## User Story
+As a user, I want tasks sorted by urgency tiers so I see what needs attention first.
+
+## Technical Context
+- Affects: `internal/handlers/handlers.go` — sorting logic before template render
+- Requires distinguishing "has specific time" vs "all-day" (midnight/00:00)
+- Todoist API may return all-day tasks with time set to 00:00:00
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+```go
+func TestSortTasksByUrgency(t *testing.T) {
+ tasks := []Task{
+ {Name: "future", DueDate: tomorrow},
+ {Name: "overdue", DueDate: yesterday},
+ {Name: "today-timed", DueDate: todayAt(14, 30)},
+ {Name: "today-allday", DueDate: todayAt(0, 0)},
+ }
+
+ sorted := SortTasksByUrgency(tasks)
+
+ // Expected order: overdue, today-timed, today-allday, future
+ assert.Equal(t, "overdue", sorted[0].Name)
+ assert.Equal(t, "today-timed", sorted[1].Name)
+ assert.Equal(t, "today-allday", sorted[2].Name)
+ assert.Equal(t, "future", sorted[3].Name)
+}
+```
+
+## Proposed Approach
+
+1. Define sort buckets with priority:
+ - Priority 0: Overdue (due_date < today)
+ - Priority 1: Today with specific time (due_date == today && time != 00:00)
+ - Priority 2: Today all-day (due_date == today && time == 00:00)
+ - Priority 3: Future (due_date > today)
+2. Within each bucket, sort by due_date ascending
+3. Implement stable sort to preserve original order within equal items
+
+## Traps to Avoid
+- **Timezone handling:** Ensure consistent timezone (user's local or UTC?) for date comparisons
+- **Null due dates:** Decide where tasks with no due date should appear (suggest: after future tasks)
+- **All-day detection:** Todoist may use different conventions; verify API response format
+
+## Affected Components
+- `internal/handlers/handlers.go`
+- `internal/handlers/handlers_test.go`
+
+## Definition of Done
+- [ ] Tasks sorted in specified order
+- [ ] Overdue tasks appear first
+- [ ] Timed tasks today before all-day tasks today
+- [ ] Future tasks appear last
+- [ ] Null due date tasks handled gracefully
+- [ ] Unit tests cover all buckets and edge cases
diff --git a/issues/007-fix-outdated-todoist-link.md b/issues/007-fix-outdated-todoist-link.md
new file mode 100644
index 0000000..5d628f2
--- /dev/null
+++ b/issues/007-fix-outdated-todoist-link.md
@@ -0,0 +1,54 @@
+# [BUG] Fix outdated Todoist task link
+
+## Description
+Fix outdated Todoist task link.
+
+## Technical Context
+- Affects: `internal/api/todoist.go` (URL construction) or stored `url` field in DB
+- Todoist may have changed their URL scheme
+- Current URLs may be returning 404 or redirecting incorrectly
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/api/todoist_test.go`
+
+```go
+func TestBuildTaskURL(t *testing.T) {
+ taskID := "123456789"
+ url := BuildTaskURL(taskID)
+
+ // Assert current Todoist URL format
+ // Option A: https://todoist.com/showTask?id=123456789
+ // Option B: https://todoist.com/app/task/123456789
+ assert.Equal(t, "https://todoist.com/app/task/123456789", url)
+}
+```
+
+## Proposed Approach
+
+1. Research current Todoist web URL format:
+ - Check Todoist docs or inspect working links in browser
+ - Likely format: `https://todoist.com/app/task/{task_id}` or `https://todoist.com/showTask?id={task_id}`
+2. Update URL builder in `internal/api/todoist.go`
+3. Consider migration for existing cached URLs in database:
+ - Option A: Update on read (lazy migration)
+ - Option B: Run migration script to update all stored URLs
+4. If URL comes from API response directly, verify we're using the correct field
+
+## Debugging Steps
+1. Find a task ID in the database
+2. Try current URL format in browser
+3. Try alternative formats to find working one
+4. Check Todoist API response for canonical URL field
+
+## Affected Components
+- `internal/api/todoist.go`
+- `internal/api/todoist_test.go`
+- Potentially `internal/store/sqlite.go` (if migration needed)
+
+## Definition of Done
+- [ ] Task links open correct Todoist page
+- [ ] URL builder uses current format
+- [ ] Existing cached URLs updated or migrated
+- [ ] Unit test validates URL format
diff --git a/issues/008-add-google-calendar-support.md b/issues/008-add-google-calendar-support.md
new file mode 100644
index 0000000..f3a1f3b
--- /dev/null
+++ b/issues/008-add-google-calendar-support.md
@@ -0,0 +1,85 @@
+# [FEATURE] Add Google Calendar support
+
+## Description
+Add Google Calendar support.
+
+## User Story
+As a user, I want my Google Calendar events displayed so I have a unified daily view.
+
+## Technical Context
+- New integration following existing patterns in `internal/api/`
+- OAuth2 flow required; store refresh token in `sync_tokens` table
+- New partial for calendar events display
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/api/gcal_test.go`
+
+```go
+func TestParseCalendarEvents(t *testing.T) {
+ // Mock Google Calendar API response
+ // Assert events parsed correctly with title, time, all-day flag
+}
+
+func TestGCalClient_FetchEvents(t *testing.T) {
+ // Mock HTTP client
+ // Assert correct API calls made
+ // Assert events returned in expected format
+}
+```
+
+### Integration Test (Red)
+Test cache/store roundtrip for events.
+
+```go
+func TestGCalEventsCache(t *testing.T) {
+ // Store events in cache
+ // Retrieve from cache
+ // Assert data integrity
+}
+```
+
+## Proposed Approach
+
+1. **OAuth Setup:**
+ - Add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` to config
+ - Implement OAuth2 flow with PKCE
+ - Store refresh token in `sync_tokens` table
+
+2. **API Client:**
+ - Create `internal/api/gcal.go` implementing interface pattern from existing clients
+ - Use `google.golang.org/api/calendar/v3`
+ - Fetch events for configurable date range (default: today + 7 days)
+
+3. **Data Model:**
+ - Add `calendar_events` table or reuse existing structure
+ - Handle all-day vs timed events
+
+4. **UI:**
+ - New partial: `partials/gcal-events.html`
+ - Integrate into existing tabs or new calendar tab
+
+## Traps to Avoid
+- **All-day events:** Google returns these differently (date vs dateTime)
+- **Timezone:** Events have their own timezone; convert to user's local
+- **Recurring events:** Decide whether to expand or show as single item
+- **Token refresh:** Handle expired access tokens gracefully
+
+## Affected Components
+- `internal/api/gcal.go` (new)
+- `internal/api/gcal_test.go` (new)
+- `internal/api/interfaces.go` (add interface)
+- `internal/config/config.go`
+- `internal/store/sqlite.go` (new table/queries)
+- `web/templates/partials/gcal-events.html` (new)
+- `cmd/dashboard/main.go` (wire up client)
+
+## Definition of Done
+- [ ] OAuth2 flow authenticates with Google
+- [ ] Events fetched from Google Calendar API
+- [ ] Events cached in SQLite
+- [ ] Events displayed in UI
+- [ ] All-day and timed events handled correctly
+- [ ] Token refresh works
+- [ ] Unit and integration tests pass
diff --git a/issues/009-keep-completed-tasks-visible.md b/issues/009-keep-completed-tasks-visible.md
new file mode 100644
index 0000000..b80eaca
--- /dev/null
+++ b/issues/009-keep-completed-tasks-visible.md
@@ -0,0 +1,54 @@
+# [FEATURE] Keep completed tasks visible until refresh
+
+## Description
+Leave completed tasks in view until refresh and don't confirm completion.
+
+## User Story
+As a user, I want completed tasks to stay visible (struck through) until I refresh so I can see my progress and undo mistakes.
+
+## Technical Context
+- Affects: `partials/todoist-tasks.html` (HTMX swap behavior), possibly JS state
+- Currently completion likely removes item via `hx-swap="delete"` or similar
+- Need to change to in-place update with completed styling
+
+## Test Strategy
+
+### E2E Test (Red)
+```
+1. Load tasks page with uncompleted task
+2. Click complete button on task
+3. Assert task still visible with .completed class (strikethrough)
+4. Assert no confirmation dialog appeared
+5. Refresh page
+6. Assert completed task no longer visible
+```
+
+## Proposed Approach
+
+1. **Change completion endpoint response:**
+ - Instead of empty response (for delete), return updated task HTML
+ - Task HTML includes completed styling (strikethrough, muted colors)
+
+2. **Update HTMX swap:**
+ - Change from `hx-swap="delete"` or `hx-swap="outerHTML"` with empty
+ - To `hx-swap="outerHTML"` with completed task HTML
+
+3. **Remove confirmation:**
+ - Remove any `hx-confirm` attribute on complete button
+ - Or remove JS confirmation dialog if present
+
+4. **Completed task styling:**
+ - Add `.completed` class with `line-through text-gray-400`
+ - Optionally add "undo" button that appears on completed tasks
+
+## Affected Components
+- `internal/handlers/handlers.go` (completion endpoint response)
+- `web/templates/partials/todoist-tasks.html`
+- `web/templates/partials/task-item.html` (if exists, for completed variant)
+
+## Definition of Done
+- [ ] Completing a task keeps it visible with strikethrough
+- [ ] No confirmation dialog on completion
+- [ ] Task disappears on page refresh
+- [ ] Optional: Undo button on completed tasks
+- [ ] E2E test confirms behavior
diff --git a/issues/010-fix-quick-add-timestamp.md b/issues/010-fix-quick-add-timestamp.md
new file mode 100644
index 0000000..3c59325
--- /dev/null
+++ b/issues/010-fix-quick-add-timestamp.md
@@ -0,0 +1,76 @@
+# [BUG] Quick add date defaults to tomorrow in evening
+
+## Description
+The date pre-filled in quick add defaults to tomorrow when used during the evening.
+
+## Symptom
+When adding a task in the evening, the date field pre-fills with tomorrow's date instead of today.
+
+## Technical Context
+- Likely a timezone or date boundary issue
+- Server may be using UTC while user expects local time
+- Or JavaScript date handling crossing midnight boundary incorrectly
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+```go
+func TestQuickAddDefaultDate(t *testing.T) {
+ tests := []struct {
+ name string
+ serverTime time.Time
+ userTZ string
+ expectedDate string
+ }{
+ {
+ name: "evening in US Eastern",
+ serverTime: time.Date(2026, 1, 21, 23, 0, 0, 0, time.UTC), // 6pm ET
+ userTZ: "America/New_York",
+ expectedDate: "2026-01-21", // Should be today, not tomorrow
+ },
+ {
+ name: "late night edge case",
+ serverTime: time.Date(2026, 1, 22, 4, 0, 0, 0, time.UTC), // 11pm ET
+ userTZ: "America/New_York",
+ expectedDate: "2026-01-21",
+ },
+ }
+ // ...
+}
+```
+
+## Proposed Approach
+
+1. **Identify the source:**
+ - Check quick-add handler: how is default date determined?
+ - Check template: is date set server-side or client-side JS?
+ - Check if using `time.Now()` (server's timezone) vs user's timezone
+
+2. **Likely fix:**
+ - If server-side: pass user's timezone preference and convert
+ - If client-side: use `new Date()` which uses browser's local time
+ - Ensure consistent timezone handling throughout
+
+3. **Verify date calculation:**
+ - `today` should be based on user's local date, not UTC date
+ - Evening in US Eastern (UTC-5) at 8pm = UTC next day 1am
+
+## Debugging Steps
+1. Check current time on server vs browser
+2. Inspect what value is sent in quick-add form
+3. Check JavaScript date initialization if client-side
+4. Check Go `time.Now()` usage if server-side
+
+## Affected Components
+- `internal/handlers/handlers.go` (quick-add endpoint/template data)
+- `web/templates/partials/` (quick-add form)
+- `web/static/` (if JS sets date)
+- Potentially user preferences for timezone
+
+## Definition of Done
+- [ ] Quick add defaults to user's local today, regardless of time
+- [ ] Works correctly in evening hours
+- [ ] Works across timezone boundaries
+- [ ] Unit test covers evening edge case
diff --git a/issues/011-add-timeline-view.md b/issues/011-add-timeline-view.md
new file mode 100644
index 0000000..49e744a
--- /dev/null
+++ b/issues/011-add-timeline-view.md
@@ -0,0 +1,86 @@
+# [FEATURE] Add timeline view
+
+## Description
+Add timeline view.
+
+## User Story
+As a user, I want a timeline/agenda view so I can see my day's schedule visually.
+
+## Technical Context
+- New view aggregating multiple data sources by time
+- Requires merging tasks + meals + (future) calendar events
+- New tab or view mode in existing UI
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+```go
+func TestBuildTimeline(t *testing.T) {
+ tasks := []Task{{Name: "Task 1", DueDate: todayAt(10, 0)}}
+ meals := []Meal{{Name: "Lunch", Date: today, Type: "lunch"}} // lunch = 12:00
+
+ timeline := BuildTimeline(tasks, meals)
+
+ assert.Len(t, timeline, 2)
+ assert.Equal(t, "Task 1", timeline[0].Title)
+ assert.Equal(t, "Lunch", timeline[1].Title)
+}
+
+func TestTimelineItemsSortedByTime(t *testing.T) {
+ // Items from different sources
+ // Assert sorted by timestamp regardless of source
+}
+```
+
+### E2E Test (Red)
+Timeline renders with correct order and time markers.
+
+## Proposed Approach
+
+1. **Data Structure:**
+ ```go
+ type TimelineItem struct {
+ Time time.Time
+ Title string
+ Source string // "task", "meal", "calendar"
+ Type string // source-specific type
+ URL string
+ Metadata map[string]any
+ }
+ ```
+
+2. **Handler:**
+ - New endpoint `GET /timeline` or extend tab handler
+ - Aggregate items from tasks, meals, (future) calendar
+ - Sort by time ascending
+ - Group by hour or time block
+
+3. **UI:**
+ - Vertical timeline with time gutter on left
+ - Color-coded by source
+ - Expandable items for details
+
+4. **Meal time mapping:**
+ - breakfast: 08:00
+ - lunch: 12:00
+ - dinner: 18:00
+ - snack: configurable or omit from timeline
+
+## Affected Components
+- `internal/handlers/handlers.go` or `internal/handlers/timeline.go` (new)
+- `internal/handlers/handlers_test.go`
+- `internal/models/types.go` (TimelineItem struct)
+- `web/templates/partials/timeline-tab.html` (new)
+- `web/templates/index.html` (add tab)
+
+## Definition of Done
+- [ ] Timeline view accessible from UI
+- [ ] Tasks with times appear at correct position
+- [ ] Meals appear at appropriate meal times
+- [ ] Items sorted chronologically
+- [ ] Visual time markers/gutter
+- [ ] Color coding by source
+- [ ] Unit tests for timeline building
+- [ ] E2E test for rendering
diff --git a/issues/013-quick-add-shopping-list.md b/issues/013-quick-add-shopping-list.md
new file mode 100644
index 0000000..e09ab92
--- /dev/null
+++ b/issues/013-quick-add-shopping-list.md
@@ -0,0 +1,81 @@
+# [FEATURE] Quick add for shopping list items
+
+## Description
+Add a quick add interface for shopping list items.
+
+## User Story
+As a user, I want to quickly add items to my shopping list without navigating away.
+
+## Technical Context
+- Requires decision on backend: PlanToEat API vs local SQLite
+- May need new table `shopping_items` if local
+- Could integrate with modal menu from issue #002
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/store/sqlite_test.go`
+
+```go
+func TestAddShoppingItem(t *testing.T) {
+ store := setupTestStore(t)
+
+ err := store.AddShoppingItem(ShoppingItem{
+ Name: "Milk",
+ Quantity: "1 gallon",
+ })
+
+ assert.NoError(t, err)
+
+ items, _ := store.GetShoppingItems()
+ assert.Len(t, items, 1)
+ assert.Equal(t, "Milk", items[0].Name)
+}
+```
+
+### E2E Test (Red)
+Quick-add form submits, item appears in list.
+
+## Proposed Approach
+
+1. **Decide backend:**
+ - Option A: PlanToEat API (if it supports shopping lists)
+ - Option B: Local SQLite table (simpler, offline-capable)
+
+2. **Database (if local):**
+ ```sql
+ CREATE TABLE shopping_items (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ quantity TEXT,
+ checked BOOLEAN DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ ```
+
+3. **Handler:**
+ - `POST /api/shopping/add` — add item
+ - `GET /api/shopping` — list items
+ - `PUT /api/shopping/{id}/check` — toggle checked
+ - `DELETE /api/shopping/{id}` — remove item
+
+4. **UI:**
+ - Quick-add form in modal menu (integrate with #002) or meals tab
+ - Simple input: item name, optional quantity
+ - List view with checkboxes
+
+## Affected Components
+- `internal/store/sqlite.go` (new table/methods)
+- `internal/store/sqlite_test.go`
+- `internal/handlers/handlers.go` (new endpoints)
+- `web/templates/partials/shopping-list.html` (new)
+- Potentially `web/templates/partials/modal-menu.html` (from #002)
+
+## Definition of Done
+- [ ] Can add items via quick-add form
+- [ ] Items persisted (API or local)
+- [ ] Items displayed in list view
+- [ ] Can check/uncheck items
+- [ ] Can delete items
+- [ ] Unit tests for store operations
+- [ ] E2E test for add flow
diff --git a/issues/015-random-landscape-background.md b/issues/015-random-landscape-background.md
new file mode 100644
index 0000000..4f65c98
--- /dev/null
+++ b/issues/015-random-landscape-background.md
@@ -0,0 +1,77 @@
+# [FEATURE] Random landscape background image
+
+## Description
+Use a random open licensed landscape image as the background, and replace it with a new one each refresh.
+
+## User Story
+As a user, I want a fresh landscape background on each visit for visual variety.
+
+## Technical Context
+- Affects page container/body styling
+- Needs external image source with open license
+- Consider performance and loading states
+
+## Test Strategy
+
+### Integration Test (Red)
+Test image endpoint returns valid image URL with appropriate license.
+
+```go
+func TestGetRandomBackgroundURL(t *testing.T) {
+ url := GetRandomBackgroundURL()
+
+ assert.NotEmpty(t, url)
+ assert.Contains(t, url, "unsplash") // or other source
+
+ // Verify URL is accessible
+ resp, err := http.Head(url)
+ assert.NoError(t, err)
+ assert.Equal(t, 200, resp.StatusCode)
+}
+```
+
+## Proposed Approach
+
+1. **Image Source Options:**
+ - **Unsplash Source API:** `https://source.unsplash.com/1920x1080/?landscape,nature`
+ - Free, no API key needed for basic use
+ - Redirects to random image
+ - **Pexels API:** Requires API key
+ - **Local curated set:** Bundle images, no external dependency
+
+2. **Implementation:**
+ - Set CSS `background-image` on body or main container
+ - Add loading state (solid color) while image loads
+ - Use `background-size: cover` and `background-position: center`
+
+3. **Template change:**
+ ```html
+ <body style="background-image: url('{{.BackgroundURL}}'); background-size: cover;">
+ ```
+
+ Or with CSS class and CSS variable.
+
+4. **Handler:**
+ - Generate/fetch random URL on each page load
+ - Pass to template context
+
+## Traps to Avoid
+- **Slow loads:** Large images can delay page render
+ - Use appropriate resolution (1920x1080 max)
+ - Consider lazy loading or progressive enhancement
+- **Image fails to load:** Have solid color fallback
+- **Caching:** Browser may cache image; ensure URL changes per request
+- **Dark text visibility:** May need overlay or text shadow for readability
+
+## Affected Components
+- `internal/handlers/handlers.go` (add background URL to context)
+- `web/templates/index.html` (apply background style)
+- `web/static/css/` (fallback and loading styles)
+
+## Definition of Done
+- [ ] Background image displays on page load
+- [ ] Different image on each refresh
+- [ ] Images are openly licensed
+- [ ] Fallback color while loading
+- [ ] Text remains readable over images
+- [ ] Integration test verifies URL validity
diff --git a/issues/016-click-task-edit-details.md b/issues/016-click-task-edit-details.md
new file mode 100644
index 0000000..a1687c9
--- /dev/null
+++ b/issues/016-click-task-edit-details.md
@@ -0,0 +1,80 @@
+# [FEATURE] Click task to open editable details
+
+## Description
+When I click a task, open details and let me update description.
+
+## User Story
+As a user, I want to click a task to view and edit its description inline.
+
+## Technical Context
+- Extends existing task display with detail view
+- Requires new endpoints for fetching and updating task details
+- HTMX-powered modal or slide-out panel
+
+## Test Strategy
+
+### Unit Test (Red)
+**File:** `internal/handlers/handlers_test.go`
+
+```go
+func TestUpdateTaskDescription(t *testing.T) {
+ // Setup mock Todoist client
+ mockClient := &MockTodoistClient{}
+ handler := NewHandler(mockClient)
+
+ // Call update endpoint
+ req := httptest.NewRequest("PUT", "/tasks/123",
+ strings.NewReader(`{"description": "Updated desc"}`))
+
+ // Assert Todoist API called with correct params
+ mockClient.AssertCalled(t, "UpdateTask", "123", "Updated desc")
+}
+```
+
+### E2E Test (Red)
+```
+1. Click on a task row
+2. Assert modal/drawer opens with task details
+3. Assert description field is editable
+4. Edit description and save
+5. Assert modal closes
+6. Assert description persisted (re-open to verify)
+```
+
+## Proposed Approach
+
+1. **Endpoints:**
+ - `GET /tasks/{id}/detail` — fetch task with full details
+ - `PUT /tasks/{id}` — update task (description, etc.)
+
+2. **UI Flow:**
+ - Click task row triggers `hx-get="/tasks/{id}/detail"`
+ - Response loads into modal or slide-out panel
+ - Form with editable description (textarea)
+ - Save button triggers `hx-put="/tasks/{id}"`
+ - On success, close modal and optionally update task row
+
+3. **Template:**
+ - New partial: `partials/task-detail.html`
+ - Contains: title (read-only or editable), description (editable), due date, labels, save/cancel buttons
+
+4. **API Integration:**
+ - Update Todoist via API when saving
+ - Update local cache
+
+## Affected Components
+- `internal/handlers/handlers.go` (new endpoints)
+- `internal/handlers/handlers_test.go`
+- `internal/api/todoist.go` (UpdateTask method if not exists)
+- `web/templates/partials/task-detail.html` (new)
+- `web/templates/partials/todoist-tasks.html` (add click handler)
+- `web/templates/index.html` (modal container if needed)
+
+## Definition of Done
+- [ ] Clicking task opens detail view
+- [ ] Description field is editable
+- [ ] Save persists to Todoist
+- [ ] Local cache updated
+- [ ] Modal/drawer closes on save
+- [ ] Unit tests for update handler
+- [ ] E2E test for full flow