diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-18 00:14:45 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-18 00:14:45 +0000 |
| commit | f08f06bef47aac2c9effb4cec650d99c2deb2dd7 (patch) | |
| tree | ae06bd2e140c678e67f2879f21b07a4114a41f73 /internal/handlers/chains_web.go | |
| parent | be4d606e9a1f5b068abcc21bbac58d1e4705ea1f (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.go | 109 |
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) +} |
