summaryrefslogtreecommitdiff
path: root/internal/handlers/chains_web.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-18 00:14:45 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-18 00:14:45 +0000
commitf08f06bef47aac2c9effb4cec650d99c2deb2dd7 (patch)
treeae06bd2e140c678e67f2879f21b07a4114a41f73 /internal/handlers/chains_web.go
parentbe4d606e9a1f5b068abcc21bbac58d1e4705ea1f (diff)
Rework Tasks tab: Chains section, checklist modal, project visibility
The flat Tasks-tab atom list was silently dumping every chain step (locked and unlocked) and dormant bucket-pool items in as ordinary undated cards, with no chain/project context and no protection against completing a locked step out of order. - CompleteNativeTask now rejects completing a locked chain task (ErrChainTaskLocked), mapped to 400 in both the widget and web complete-atom handlers. - Chain tasks and dormant bucket items are excluded from the flat atom list; a new "Chains" section shows one card per active/paused chain with the current step and N/M progress. - New chain checklist modal (GET /chains/{id}) lists every position in order with pause/resume/abandon -- the web view originally deferred as Android-only. - Fixed a real bug this surfaced: resuming a paused chain only flipped the status flag, never unlocking the deferred successor, so a chain paused right after a completion stayed stuck forever. SetChainStatus now catches up the deferred advancement on resume, idempotently. - Atom cards gained a project-name chip for general visibility. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'internal/handlers/chains_web.go')
-rw-r--r--internal/handlers/chains_web.go109
1 files changed, 109 insertions, 0 deletions
diff --git a/internal/handlers/chains_web.go b/internal/handlers/chains_web.go
new file mode 100644
index 0000000..862bec3
--- /dev/null
+++ b/internal/handlers/chains_web.go
@@ -0,0 +1,109 @@
+package handlers
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/go-chi/chi/v5"
+
+ "task-dashboard/internal/models"
+ "task-dashboard/internal/store"
+)
+
+// BuildChainSummaries returns one summary per active/paused chain --
+// project name, the currently-unlocked task (nil if none, e.g. a chain
+// that just completed and hasn't been re-fetched into the active/paused
+// set yet), and 1-indexed position/total for a "N/M" progress display.
+// Backs the Tasks tab's Chains section.
+func BuildChainSummaries(s *store.Store) ([]models.ChainSummary, error) {
+ chains, err := s.GetChains()
+ if err != nil {
+ return nil, err
+ }
+ summaries := make([]models.ChainSummary, 0, len(chains))
+ for _, chain := range chains {
+ project, err := s.GetProjectByID(chain.ProjectID)
+ if err != nil {
+ continue // orphaned project reference -- skip rather than fail the whole tab
+ }
+ tasks, err := s.GetChainTasks(chain.ID)
+ if err != nil {
+ return nil, err
+ }
+ summary := models.ChainSummary{Chain: chain, ProjectName: project.Name, Total: len(tasks)}
+ for i, t := range tasks {
+ if t.ChainUnlocked {
+ task := t
+ summary.CurrentTask = &task
+ summary.Position = i + 1
+ break
+ }
+ }
+ summaries = append(summaries, summary)
+ }
+ return summaries, nil
+}
+
+// HandleChainDetailView renders the full ordered checklist for a chain --
+// locked and unlocked tasks both -- for the web Tasks tab's chain modal.
+// This is the "visible in the tasks list" requirement from the chains
+// design spec, met on web (the spec originally scoped this to Android
+// only; extended to web per user request).
+func (h *Handler) HandleChainDetailView(w http.ResponseWriter, r *http.Request) {
+ id := chi.URLParam(r, "id")
+ chain, err := h.store.GetChain(id)
+ if err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ http.Error(w, "chain not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+ project, err := h.store.GetProjectByID(chain.ProjectID)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+ tasks, err := h.store.GetChainTasks(id)
+ if err != nil {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ return
+ }
+ data := struct {
+ Chain models.Chain
+ ProjectName string
+ Tasks []models.Task
+ }{Chain: *chain, ProjectName: project.Name, Tasks: tasks}
+ HTMLResponse(w, h.renderer, "chain-detail", data)
+}
+
+// HandleChainPause, HandleChainResume, and HandleChainAbandon are the
+// session-authed web equivalents of the widget API's chain-status
+// endpoints, for the Tasks tab's chain modal.
+func (h *Handler) HandleChainPause(w http.ResponseWriter, r *http.Request) {
+ h.setChainStatusWeb(w, r, "paused")
+}
+
+func (h *Handler) HandleChainResume(w http.ResponseWriter, r *http.Request) {
+ h.setChainStatusWeb(w, r, "active")
+}
+
+func (h *Handler) HandleChainAbandon(w http.ResponseWriter, r *http.Request) {
+ h.setChainStatusWeb(w, r, "abandoned")
+}
+
+func (h *Handler) setChainStatusWeb(w http.ResponseWriter, r *http.Request, status string) {
+ id := chi.URLParam(r, "id")
+ if err := h.store.SetChainStatus(id, status); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ http.Error(w, "chain not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, "failed to update chain", http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("HX-Reswap", "none")
+ w.Header().Set("HX-Trigger", "refresh-tasks")
+ w.WriteHeader(http.StatusOK)
+}