From c060e13b419b698ff6a795ca3d16ed195ead3991 Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:20:00 -0700 Subject: [PATCH] Coop: surface per-gift countdowns in !coop status Each gift now has its own 6h voting window, but the only places the remaining time was visible were the original arrival post and any edit-triggered re-render after a vote. Mid-window the displayed countdown was stale. !coop status now lists pending gifts (anything with vote_result still NULL on this run) with current open/leave tally and a fresh per-gift countdown derived from expires_at. The party can poll this any time to see what's about to close. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/plugin/coop_dungeon_render.go | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/internal/plugin/coop_dungeon_render.go b/internal/plugin/coop_dungeon_render.go index 9bcd42d..1fb50d4 100644 --- a/internal/plugin/coop_dungeon_render.go +++ b/internal/plugin/coop_dungeon_render.go @@ -216,5 +216,42 @@ func renderCoopStatus(p *AdventurePlugin, run *CoopRun, members []CoopMember) st sb.WriteString("\n") sb.WriteString(renderCoopOddsLine(run, members, bets)) } + + // Pending gift list with per-gift countdowns. Voting windows expire + // independently throughout the day, so this is the canonical place to + // see what's still open. + if run.Status == "active" { + all, _ := loadAllCoopGifts(run.ID) + var pending []CoopGift + for _, g := range all { + if g.VoteResult == "" { + pending = append(pending, g) + } + } + if len(pending) > 0 { + sb.WriteString("\n\nPending gifts:\n") + for _, g := range pending { + opens, leaves := 0, 0 + for _, v := range g.Votes { + if v == "open" { + opens++ + } else if v == "leave" { + leaves++ + } + } + countdown := "—" + if g.ExpiresAt != nil { + remaining := time.Until(*g.ExpiresAt).Truncate(time.Minute) + if remaining > 0 { + countdown = remaining.String() + } else { + countdown = "closing" + } + } + sb.WriteString(fmt.Sprintf(" #%d (day %d) — open %d / leave %d — closes in %s\n", + g.ID, g.Day, opens, leaves, countdown)) + } + } + } return sb.String() }