summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-25 11:56:29 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-25 11:56:29 -1000
commitec8a9c0ea46dec7d26caa763e3adefcaf3fc7552 (patch)
tree1f91bbc7ec87314189a441c53b7c3b25f1817db0 /internal/handlers
parent83beddfab9584ae4b64a782c978236472b6d5745 (diff)
Fix bugs and add bug management scripts
Bug fixes: - #36: Hide recurring tasks until due day (add IsRecurring to Task/Atom) - Trello cards missing: change filter=visible to filter=open - Build fix: add missing fmt import in atom.go Infrastructure: - Add scripts/bugs and scripts/resolve-bug for DB bug tracking - Remove issues/ directory (bugs now tracked in DB) - Add timeline_logic_test.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go4
-rw-r--r--internal/handlers/timeline_logic_test.go160
2 files changed, 164 insertions, 0 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 5014f39..5c86ce2 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -996,6 +996,10 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) {
var currentAtoms, futureAtoms []models.Atom
for _, a := range atoms {
+ // Don't show recurring tasks until the day they're due
+ if a.IsRecurring && a.IsFuture {
+ continue
+ }
if a.IsFuture {
futureAtoms = append(futureAtoms, a)
} else {
diff --git a/internal/handlers/timeline_logic_test.go b/internal/handlers/timeline_logic_test.go
new file mode 100644
index 0000000..a0576d6
--- /dev/null
+++ b/internal/handlers/timeline_logic_test.go
@@ -0,0 +1,160 @@
+package handlers
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "task-dashboard/internal/models"
+ "task-dashboard/internal/store"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+// MockCalendarClient implements GoogleCalendarAPI interface for testing
+type MockCalendarClient struct {
+ Events []models.CalendarEvent
+ Err error
+}
+
+func (m *MockCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error) {
+ return m.Events, m.Err
+}
+
+func (m *MockCalendarClient) GetEventsByDateRange(ctx context.Context, start, end time.Time) ([]models.CalendarEvent, error) {
+ return m.Events, m.Err
+}
+
+func setupTestStore(t *testing.T) *store.Store {
+ t.Helper()
+ tempDir := t.TempDir()
+ dbPath := filepath.Join(tempDir, "test.db")
+ migrationDir := filepath.Join(tempDir, "migrations")
+
+ if err := os.MkdirAll(migrationDir, 0755); err != nil {
+ t.Fatalf("Failed to create migration dir: %v", err)
+ }
+
+ schema := `
+ CREATE TABLE IF NOT EXISTS tasks (
+ id TEXT PRIMARY KEY,
+ content TEXT NOT NULL,
+ description TEXT,
+ project_id TEXT,
+ project_name TEXT,
+ due_date DATETIME,
+ priority INTEGER DEFAULT 1,
+ completed BOOLEAN DEFAULT FALSE,
+ labels TEXT,
+ url TEXT,
+ created_at DATETIME,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS meals (
+ id TEXT PRIMARY KEY,
+ recipe_name TEXT NOT NULL,
+ date DATETIME,
+ meal_type TEXT,
+ recipe_url TEXT,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS boards (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS cards (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ board_id TEXT NOT NULL,
+ list_id TEXT,
+ list_name TEXT,
+ due_date DATETIME,
+ url TEXT,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );
+ `
+ if err := os.WriteFile(filepath.Join(migrationDir, "001_init.sql"), []byte(schema), 0644); err != nil {
+ t.Fatalf("Failed to write migration file: %v", err)
+ }
+
+ // Initialize store (this creates tables)
+ s, err := store.New(dbPath, migrationDir)
+ if err != nil {
+ t.Fatalf("Failed to create store: %v", err)
+ }
+ return s
+}
+
+func TestBuildTimeline(t *testing.T) {
+ s := setupTestStore(t)
+
+ // Fix a base time: 2023-01-01 08:00:00
+ baseTime := time.Date(2023, 1, 1, 8, 0, 0, 0, time.UTC)
+
+ // Task: 10:00
+ taskDate := baseTime.Add(2 * time.Hour)
+ s.SaveTasks([]models.Task{
+ {ID: "t1", Content: "Task 1", DueDate: &taskDate},
+ })
+
+ // Meal: Lunch (defaults to 12:00)
+ mealDate := baseTime // Date part matters
+ s.SaveMeals([]models.Meal{
+ {ID: "m1", RecipeName: "Lunch", Date: mealDate, MealType: "lunch"},
+ })
+
+ // Card: 14:00
+ cardDate := baseTime.Add(6 * time.Hour)
+ s.SaveBoards([]models.Board{
+ {
+ ID: "b1",
+ Name: "Board 1",
+ Cards: []models.Card{
+ {ID: "c1", Name: "Card 1", DueDate: &cardDate, ListID: "l1"},
+ },
+ },
+ })
+
+ // Calendar Event: 09:00
+ eventDate := baseTime.Add(1 * time.Hour)
+ mockCal := &MockCalendarClient{
+ Events: []models.CalendarEvent{
+ {ID: "e1", Summary: "Event 1", Start: eventDate, End: eventDate.Add(1 * time.Hour)},
+ },
+ }
+
+ // Test Range: Full Day
+ start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
+ end := time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC)
+
+ items, err := BuildTimeline(context.Background(), s, mockCal, start, end)
+ if err != nil {
+ t.Fatalf("BuildTimeline failed: %v", err)
+ }
+
+ if len(items) != 4 {
+ t.Errorf("Expected 4 items, got %d", len(items))
+ }
+
+ // Expected Order:
+ // 1. Event (09:00)
+ // 2. Task (10:00)
+ // 3. Meal (12:00)
+ // 4. Card (14:00)
+
+ if items[0].Type != models.TimelineItemTypeEvent {
+ t.Errorf("Expected item 0 to be Event, got %s", items[0].Type)
+ }
+ if items[1].Type != models.TimelineItemTypeTask {
+ t.Errorf("Expected item 1 to be Task, got %s", items[1].Type)
+ }
+ if items[2].Type != models.TimelineItemTypeMeal {
+ t.Errorf("Expected item 2 to be Meal, got %s", items[2].Type)
+ }
+ if items[3].Type != models.TimelineItemTypeCard {
+ t.Errorf("Expected item 3 to be Card, got %s", items[3].Type)
+ }
+}