summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-24 06:20:52 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-24 06:20:52 +0000
commit5c659d9046add8c77bd7699961a910505dcb4bc2 (patch)
tree8bde9a62d98d2a21f030d4c6cbd3ceecb9a7adf8 /internal/handlers
parentc46c135f52090a082660cb06131282796f5cd592 (diff)
Remove confirmed-orphaned routes, handlers, and templatesHEADmaster
From the sitemap audit: /tabs/meals (same dead-tab pattern as the removed /tabs/conditions, only reachable via ?tab=meals with no nav button), /partials/lists (superseded by inline .Lists rendering in trello-board.html), /shopping/toggle and /shopping/mode/{store}/toggle (superseded by the one-way complete/filter model), plus the orphaned trello-boards.html and error-banner.html templates that nothing rendered or included. Rewrote the meals grouping test to exercise groupMeals() directly since that logic is still live via the timeline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go31
-rw-r--r--internal/handlers/handlers_test.go271
-rw-r--r--internal/handlers/shopping.go83
-rw-r--r--internal/handlers/tab_state_test.go8
4 files changed, 12 insertions, 381 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index fc75b61..69e1c65 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -787,23 +787,6 @@ func (h *Handler) HandleUnifiedAdd(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
-// HandleGetListsOptions returns HTML options for lists in a given board
-func (h *Handler) HandleGetListsOptions(w http.ResponseWriter, r *http.Request) {
- boardID := r.URL.Query().Get("board_id")
- if boardID == "" {
- JSONError(w, http.StatusBadRequest, "board_id is required", nil)
- return
- }
-
- lists, err := h.trelloClient.GetLists(r.Context(), boardID)
- if err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to fetch lists", err)
- return
- }
-
- HTMLResponse(w, h.renderer, "lists-options", lists)
-}
-
// taskDetailData is what the "task-detail" template renders (the modal
// opened from any tab's task card). Recurrence fields are doot-native only.
type taskDetailData struct {
@@ -1234,20 +1217,6 @@ type CombinedMeal struct {
Meals []models.Meal // original meal records
}
-// HandleTabMeals renders the Meals tab (PlanToEat)
-func (h *Handler) HandleTabMeals(w http.ResponseWriter, r *http.Request) {
- startDate := config.Today()
- endDate := startDate.AddDate(0, 0, 7)
-
- meals, err := h.store.GetMeals(startDate, endDate)
- if err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to fetch meals", err)
- return
- }
-
- HTMLResponse(w, h.renderer, "meals-tab", struct{ Meals []CombinedMeal }{groupMeals(meals)})
-}
-
// mealTypeOrder returns sort order for meal types
func mealTypeOrder(mealType string) int {
switch mealType {
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
index ca24778..61fb75d 100644
--- a/internal/handlers/handlers_test.go
+++ b/internal/handlers/handlers_test.go
@@ -819,169 +819,6 @@ func TestShoppingComplete_ExternalItemMarkedChecked(t *testing.T) {
}
}
-// TestShoppingToggle_UpdatesItemState verifies toggle correctly updates state
-func TestShoppingToggle_UpdatesItemState(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- h := &Handler{
- store: db,
- renderer: newTestRenderer(),
- config: &config.Config{},
- }
-
- // Add user item
- _ = db.SaveUserShoppingItem("Toggle Item", "TestStore")
- items, _ := db.GetUserShoppingItems()
- itemID := items[0].ID
-
- // Toggle to checked
- req := httptest.NewRequest("POST", "/shopping/toggle", nil)
- req.Form = map[string][]string{
- "id": {fmt.Sprintf("user-%d", itemID)},
- "source": {"user"},
- "checked": {"true"},
- }
- w := httptest.NewRecorder()
- h.HandleShoppingToggle(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Verify item is checked
- items, _ = db.GetUserShoppingItems()
- if !items[0].Checked {
- t.Error("Expected item to be checked after toggle")
- }
-
- // Toggle back to unchecked
- req = httptest.NewRequest("POST", "/shopping/toggle", nil)
- req.Form = map[string][]string{
- "id": {fmt.Sprintf("user-%d", itemID)},
- "source": {"user"},
- "checked": {"false"},
- }
- w = httptest.NewRecorder()
- h.HandleShoppingToggle(w, req)
-
- items, _ = db.GetUserShoppingItems()
- if items[0].Checked {
- t.Error("Expected item to be unchecked after second toggle")
- }
-}
-
-// TestShoppingToggle_ExternalSources verifies toggle works for external sources
-func TestShoppingToggle_ExternalSources(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- h := &Handler{
- store: db,
- renderer: newTestRenderer(),
- config: &config.Config{},
- }
-
- tests := []struct {
- source string
- itemID string
- }{
- {"trello", "trello-toggle-123"},
- {"plantoeat", "pte-toggle-456"},
- }
-
- for _, tc := range tests {
- t.Run(tc.source, func(t *testing.T) {
- // Toggle to checked
- req := httptest.NewRequest("POST", "/shopping/toggle", nil)
- req.Form = map[string][]string{
- "id": {tc.itemID},
- "source": {tc.source},
- "checked": {"true"},
- }
- w := httptest.NewRecorder()
- h.HandleShoppingToggle(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- checks, _ := db.GetShoppingItemChecks(tc.source)
- if !checks[tc.itemID] {
- t.Errorf("Expected %s item to be checked", tc.source)
- }
- })
- }
-}
-
-// TestShoppingToggle_UnknownSource verifies error for unknown source
-func TestShoppingToggle_UnknownSource(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- h := &Handler{
- store: db,
- config: &config.Config{},
- }
-
- req := httptest.NewRequest("POST", "/shopping/toggle", nil)
- req.Form = map[string][]string{
- "id": {"unknown-123"},
- "source": {"unknown"},
- "checked": {"true"},
- }
- w := httptest.NewRecorder()
- h.HandleShoppingToggle(w, req)
-
- if w.Code != http.StatusBadRequest {
- t.Errorf("Expected status 400 for unknown source, got %d", w.Code)
- }
-}
-
-// TestShoppingModeToggle_ReturnsUpdatedList verifies shopping mode toggle returns items
-func TestShoppingModeToggle_ReturnsUpdatedList(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- h := &Handler{
- store: db,
- renderer: newTestRenderer(),
- config: &config.Config{},
- }
-
- // Add items
- _ = db.SaveUserShoppingItem("Item 1", "TestStore")
- _ = db.SaveUserShoppingItem("Item 2", "TestStore")
-
- items, _ := db.GetUserShoppingItems()
- itemID := items[0].ID
-
- // Toggle in shopping mode
- req := httptest.NewRequest("POST", "/shopping/mode/TestStore/toggle", nil)
- req.Form = map[string][]string{
- "id": {fmt.Sprintf("user-%d", itemID)},
- "source": {"user"},
- "checked": {"true"},
- }
-
- rctx := chi.NewRouteContext()
- rctx.URLParams.Add("store", "TestStore")
- req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
-
- w := httptest.NewRecorder()
- h.HandleShoppingModeToggle(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Response should be HTML (items list)
- contentType := w.Header().Get("Content-Type")
- if !strings.Contains(contentType, "text/html") {
- t.Errorf("Expected HTML response, got %s", contentType)
- }
-}
-
// TestShoppingTabFiltersCheckedItems verifies checked items excluded from tab
func TestShoppingTabFiltersCheckedItems(t *testing.T) {
db, cleanup := setupTestDB(t)
@@ -1057,26 +894,6 @@ func TestHandleTabTasks(t *testing.T) {
}
}
-func TestHandleTabMeals(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- h := &Handler{
- store: db,
- renderer: newTestRenderer(),
- config: &config.Config{CacheTTLMinutes: 5},
- }
-
- req := httptest.NewRequest("GET", "/tabs/meals", nil)
- w := httptest.NewRecorder()
-
- h.HandleTabMeals(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-}
-
func TestHandleTabPlanning(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
@@ -2120,35 +1937,6 @@ func TestHandleSetTaskRecurrence_InvalidFreq_Returns400(t *testing.T) {
}
}
-// =============================================================================
-// HandleGetListsOptions template tests
-// =============================================================================
-
-// TestHandleGetListsOptions_RendersTemplate verifies that HandleGetListsOptions uses
-// the renderer with the "lists-options" template.
-func TestHandleGetListsOptions_RendersTemplate(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- h.trelloClient = &mockTrelloClient{boards: []models.Board{}}
-
- req := httptest.NewRequest("GET", "/trello/lists?board_id=board1", nil)
- w := httptest.NewRecorder()
- h.HandleGetListsOptions(w, req)
-
- mr := h.renderer.(*MockRenderer)
- var found bool
- for _, call := range mr.Calls {
- if call.Name == "lists-options" {
- found = true
- break
- }
- }
- if !found {
- t.Error("Expected renderer to be called with 'lists-options' template")
- }
-}
-
// TestHandleSyncSources_AddsLogEntry verifies that syncing sources creates a sync log entry.
func TestHandleSyncSources_AddsLogEntry(t *testing.T) {
h, cleanup := setupTestHandler(t)
@@ -2339,67 +2127,32 @@ func TestHandleTabPlanning_TomorrowBoundary(t *testing.T) {
}
// =============================================================================
-// HandleTabMeals grouping test
+// groupMeals grouping test
// =============================================================================
-// TestHandleTabMeals_GroupingMergesRecipes verifies that multiple meals sharing
-// the same date+mealType are combined into a single CombinedMeal entry whose
-// RecipeNames contains all merged recipe names.
-func TestHandleTabMeals_GroupingMergesRecipes(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
+// TestGroupMeals_MergesRecipes verifies that multiple meals sharing the same
+// date+mealType are combined into a single CombinedMeal entry whose
+// RecipeNames contains all merged recipe names. groupMeals is still live
+// logic used by the timeline (see timeline_logic.go).
+func TestGroupMeals_MergesRecipes(t *testing.T) {
today := config.Today()
meals := []models.Meal{
{ID: "m1", RecipeName: "Pasta", Date: today, MealType: "dinner", RecipeURL: "http://example.com/pasta"},
{ID: "m2", RecipeName: "Salad", Date: today, MealType: "dinner", RecipeURL: "http://example.com/salad"},
{ID: "m3", RecipeName: "Oatmeal", Date: today, MealType: "breakfast", RecipeURL: "http://example.com/oatmeal"},
}
- if err := db.SaveMeals(meals); err != nil {
- t.Fatalf("Failed to save meals: %v", err)
- }
- renderer := NewMockRenderer()
- h := &Handler{
- store: db,
- renderer: renderer,
- config: &config.Config{CacheTTLMinutes: 5},
- }
-
- req := httptest.NewRequest("GET", "/tabs/meals", nil)
- w := httptest.NewRecorder()
- h.HandleTabMeals(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected 200, got %d", w.Code)
- }
-
- var mealsCall *RenderCall
- for i, call := range renderer.Calls {
- if call.Name == "meals-tab" {
- c := renderer.Calls[i]
- mealsCall = &c
- break
- }
- }
- if mealsCall == nil {
- t.Fatal("Expected meals-tab to be rendered")
- }
-
- data, ok := mealsCall.Data.(struct{ Meals []CombinedMeal })
- if !ok {
- t.Fatalf("Expected meals data struct, got %T", mealsCall.Data)
- }
+ combined := groupMeals(meals)
// m1 + m2 share date+dinner → 1 CombinedMeal; m3 is breakfast → 1 CombinedMeal
- if len(data.Meals) != 2 {
- t.Errorf("Expected 2 combined meals, got %d", len(data.Meals))
+ if len(combined) != 2 {
+ t.Errorf("Expected 2 combined meals, got %d", len(combined))
}
var dinner *CombinedMeal
- for i := range data.Meals {
- if data.Meals[i].MealType == "dinner" {
- dinner = &data.Meals[i]
+ for i := range combined {
+ if combined[i].MealType == "dinner" {
+ dinner = &combined[i]
break
}
}
diff --git a/internal/handlers/shopping.go b/internal/handlers/shopping.go
index e8e80da..1c58126 100644
--- a/internal/handlers/shopping.go
+++ b/internal/handlers/shopping.go
@@ -73,47 +73,6 @@ func (h *Handler) HandleShoppingQuickAdd(w http.ResponseWriter, r *http.Request)
}{allStores, grouped})
}
-// HandleShoppingToggle toggles a shopping item's checked state
-func (h *Handler) HandleShoppingToggle(w http.ResponseWriter, r *http.Request) {
- if err := r.ParseForm(); err != nil {
- JSONError(w, http.StatusBadRequest, "Failed to parse form", err)
- return
- }
-
- id := r.FormValue("id")
- source := r.FormValue("source")
- checked := r.FormValue("checked") == "true"
-
- switch source {
- case "user":
- var userID int64
- if _, err := fmt.Sscanf(id, "user-%d", &userID); err != nil {
- JSONError(w, http.StatusBadRequest, "Invalid user item ID", err)
- return
- }
- if err := h.store.ToggleUserShoppingItem(userID, checked); err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
- return
- }
- case "trello", "plantoeat":
- // Store checked state locally for external sources
- if err := h.store.SetShoppingItemChecked(source, id, checked); err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
- return
- }
- default:
- JSONError(w, http.StatusBadRequest, "Unknown source", nil)
- return
- }
-
- // Return refreshed shopping tab
- stores := h.aggregateShoppingLists(r.Context())
- HTMLResponse(w, h.renderer, "shopping-tab", struct {
- Stores []models.ShoppingStore
- Grouped bool
- }{stores, true})
-}
-
// HandleShoppingMode renders the focused shopping mode for a single store
func (h *Handler) HandleShoppingMode(w http.ResponseWriter, r *http.Request) {
storeName := chi.URLParam(r, "store")
@@ -143,48 +102,6 @@ func (h *Handler) HandleShoppingMode(w http.ResponseWriter, r *http.Request) {
HTMLResponse(w, h.renderer, "shopping-mode.html", data)
}
-// HandleShoppingModeToggle toggles an item in shopping mode and returns updated list
-func (h *Handler) HandleShoppingModeToggle(w http.ResponseWriter, r *http.Request) {
- storeName := chi.URLParam(r, "store")
- if err := r.ParseForm(); err != nil {
- JSONError(w, http.StatusBadRequest, "Failed to parse form", err)
- return
- }
-
- id := r.FormValue("id")
- source := r.FormValue("source")
- checked := r.FormValue("checked") == "true"
-
- // Toggle the item
- switch source {
- case "user":
- var userID int64
- if _, err := fmt.Sscanf(id, "user-%d", &userID); err != nil {
- JSONError(w, http.StatusBadRequest, "Invalid user item ID", err)
- return
- }
- if err := h.store.ToggleUserShoppingItem(userID, checked); err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
- return
- }
- case "trello", "plantoeat":
- if err := h.store.SetShoppingItemChecked(source, id, checked); err != nil {
- JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
- return
- }
- }
-
- // URL decode the store name
- storeName, _ = url.QueryUnescape(storeName)
-
- // Return updated item list partial
- allStores := h.aggregateShoppingLists(r.Context())
- HTMLResponse(w, h.renderer, "shopping-mode-items", struct {
- StoreName string
- Items []models.UnifiedShoppingItem
- }{storeName, models.FlattenItemsForStore(allStores, storeName)})
-}
-
// HandleShoppingModeComplete removes an item from the shopping list
func (h *Handler) HandleShoppingModeComplete(w http.ResponseWriter, r *http.Request) {
storeName := chi.URLParam(r, "store")
diff --git a/internal/handlers/tab_state_test.go b/internal/handlers/tab_state_test.go
index 9b34033..bf833af 100644
--- a/internal/handlers/tab_state_test.go
+++ b/internal/handlers/tab_state_test.go
@@ -24,7 +24,6 @@ func TestHandleDashboard_TabState(t *testing.T) {
<body>
<button class="tab-button tab-button-active" hx-get="/tabs/tasks">Tasks</button>
<button class="tab-button tab-button-active" hx-get="/tabs/planning">Planning</button>
-<button class="tab-button tab-button-active" hx-get="/tabs/meals">Meals</button>
</body>
</html>`
_, err := w.Write([]byte(html))
@@ -63,13 +62,6 @@ func TestHandleDashboard_TabState(t *testing.T) {
expectedActive: `class="tab-button tab-button-active"`,
expectedHxGet: `hx-get="/tabs/planning"`,
},
- {
- name: "meals tab from query param",
- url: "/?tab=meals",
- expectedTab: "meals",
- expectedActive: `class="tab-button tab-button-active"`,
- expectedHxGet: `hx-get="/tabs/meals"`,
- },
}
for _, tt := range tests {