From 583f90c5dedf0235fa45557359b0e6e7dd62b0f0 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 21 Jan 2026 22:53:37 -1000 Subject: Implement 10 UI/UX improvements and bug fixes - Fix outdated Todoist task URL format (showTask -> app/task) - Fix quick-add date defaulting to tomorrow in evening (client-side JS) - Add tap-to-expand for task descriptions with checkbox completion - Add visual differentiation: overdue (red), future (gray), today (normal) - Sort tasks by urgency: overdue > today-timed > today-allday > future - Keep completed tasks visible with strikethrough until refresh - Add random Unsplash landscape background with content overlay - Hide future tasks behind collapsible fold with count badge - Unified modal menu for Quick Add + Bug Report (Ctrl+K shortcut) - Click task title to edit description in modal Co-Authored-By: Claude Opus 4.5 --- internal/handlers/tabs.go | 87 +++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 33 deletions(-) (limited to 'internal/handlers/tabs.go') diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go index bd15710..2f22c44 100644 --- a/internal/handlers/tabs.go +++ b/internal/handlers/tabs.go @@ -25,6 +25,25 @@ func isActionableList(name string) bool { strings.Contains(lower, "today") } +// atomUrgencyTier returns the urgency tier for sorting: +// 0: Overdue, 1: Today with time, 2: Today all-day, 3: Future, 4: No due date +func atomUrgencyTier(a models.Atom) int { + if a.DueDate == nil { + return 4 // No due date + } + if a.IsOverdue { + return 0 // Overdue + } + if a.IsFuture { + return 3 // Future + } + // Due today + if a.HasSetTime { + return 1 // Today with specific time + } + return 2 // Today all-day +} + // TabsHandler handles tab-specific rendering with Atom model type TabsHandler struct { store *store.Store @@ -88,41 +107,31 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { } } - // Compute UI fields (IsOverdue, HasSetTime) + // Compute UI fields (IsOverdue, IsFuture, HasSetTime) for i := range atoms { atoms[i].ComputeUIFields() } - // Sort atoms: by DueDate (earliest first), then by HasSetTime, then by Priority + // Sort atoms by urgency tiers: + // 1. Overdue (before today) + // 2. Today with specific time + // 3. Today all-day (midnight) + // 4. Future + // 5. No due date + // Within each tier: sort by due date/time, then by priority sort.SliceStable(atoms, func(i, j int) bool { - // Handle nil due dates (push to end) - if atoms[i].DueDate == nil && atoms[j].DueDate != nil { - return false - } - if atoms[i].DueDate != nil && atoms[j].DueDate == nil { - return true + // Compute urgency tier (lower = more urgent) + tierI := atomUrgencyTier(atoms[i]) + tierJ := atomUrgencyTier(atoms[j]) + + if tierI != tierJ { + return tierI < tierJ } - // Both have due dates + // Same tier: sort by due date/time if both have dates if atoms[i].DueDate != nil && atoms[j].DueDate != nil { - // Compare by date only (ignore time) - dateI := atoms[i].DueDate.Truncate(24 * time.Hour) - dateJ := atoms[j].DueDate.Truncate(24 * time.Hour) - - if !dateI.Equal(dateJ) { - return dateI.Before(dateJ) - } - - // Same day: tasks with set times come before midnight tasks - if atoms[i].HasSetTime != atoms[j].HasSetTime { - return atoms[i].HasSetTime - } - - // Both have set times or both are midnight, sort by actual time - if atoms[i].HasSetTime && atoms[j].HasSetTime { - if !atoms[i].DueDate.Equal(*atoms[j].DueDate) { - return atoms[i].DueDate.Before(*atoms[j].DueDate) - } + if !atoms[i].DueDate.Equal(*atoms[j].DueDate) { + return atoms[i].DueDate.Before(*atoms[j].DueDate) } } @@ -130,15 +139,27 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { return atoms[i].Priority > atoms[j].Priority }) + // Partition atoms into current (overdue + today) and future + var currentAtoms, futureAtoms []models.Atom + for _, a := range atoms { + if a.IsFuture { + futureAtoms = append(futureAtoms, a) + } else { + currentAtoms = append(currentAtoms, a) + } + } + // Render template data := struct { - Atoms []models.Atom - Boards []models.Board - Today string + Atoms []models.Atom // Current tasks (overdue + today) + FutureAtoms []models.Atom // Future tasks (hidden by default) + Boards []models.Board + Today string }{ - Atoms: atoms, - Boards: boards, - Today: time.Now().Format("2006-01-02"), + Atoms: currentAtoms, + FutureAtoms: futureAtoms, + Boards: boards, + Today: time.Now().Format("2006-01-02"), } if err := h.templates.ExecuteTemplate(w, "tasks-tab", data); err != nil { -- cgit v1.2.3