diff --git a/internal/storage/mischief.go b/internal/storage/mischief.go index efd14ae..5320ef1 100644 --- a/internal/storage/mischief.go +++ b/internal/storage/mischief.go @@ -117,18 +117,17 @@ func ResolveMischiefOrder(guid, status, detail string) (MischiefOrder, error) { return MischiefOrder{}, fmt.Errorf("mischief: bad verdict %q", status) } now := nowUnix() - res, err := Get().Exec( + if _, err := Get().Exec( `UPDATE mischief_orders SET status = ?, detail = ?, updated_at = ? WHERE guid = ? AND status = ?`, status, detail, now, guid, MischiefPending, - ) - if err != nil { + ); err != nil { return MischiefOrder{}, fmt.Errorf("mischief: resolve order: %w", err) } - if n, _ := res.RowsAffected(); n == 0 { - // Either it doesn't exist or it's already terminal. Tell the caller which. - return MischiefOrderByGUID(guid) - } + // Whether the update moved a pending order or matched nothing (missing, or + // already terminal from an earlier verdict), the current row is the + // authoritative answer — reading it back is the single path either way, and + // MischiefOrderByGUID turns a missing row into ErrNoSuchOrder for the caller. return MischiefOrderByGUID(guid) } diff --git a/internal/web/templates/who.html b/internal/web/templates/who.html index 47beff6..1d2df75 100644 --- a/internal/web/templates/who.html +++ b/internal/web/templates/who.html @@ -167,13 +167,17 @@ function txt(id, v) { var el = document.getElementById(id); if (el && v != null) el.textContent = v; } + var timer = null; function refresh() { fetch('/api/adventure/who/' + encodeURIComponent(token), { headers: { 'Accept': 'application/json' } }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (data) { if (!data) return; if (!data.live) { + // Off the board entirely (expedition ended, opted out): nothing more to + // poll for, so stop the timer rather than hammer a token that's gone. txt('who-where', 'Back in town'); + if (timer) clearInterval(timer); return; } var d = data.detail || {}; @@ -187,11 +191,17 @@ txt('who-threat', d.threat_level); setBar(); } - if (mark.OnRun) txt('who-where', mark.Where); + // Keep the location line honest in both directions: a mark that came back + // to town while still on the board must stop showing its old expedition. + if (mark.OnRun) { + txt('who-where', mark.Where); + } else { + txt('who-where', 'In town' + (mark.Idle ? ' · ' + mark.Idle : '')); + } }) .catch(function () { /* transient — next tick will do */ }); } - setInterval(refresh, 60000); + timer = setInterval(refresh, 60000); })(); {{end}} diff --git a/internal/web/who.go b/internal/web/who.go index 386fde9..1f22f45 100644 --- a/internal/web/who.go +++ b/internal/web/who.go @@ -54,7 +54,6 @@ var abilityLabels = [6]string{"STR", "DEX", "CON", "INT", "WIS", "CHA"} type whoPage struct { pageData Mark RosterView - Stale bool HasDetail bool Detail whoDetail Abilities []abilityRow @@ -90,7 +89,6 @@ func (s *Server) handleAdventureWho(w http.ResponseWriter, r *http.Request) { page := whoPage{ pageData: base, Mark: toRosterView(entry), - Stale: storage.RosterSnapshotAt() == 0, } if d, abil, ok := decodeWhoDetail(entry.Detail); ok { page.HasDetail = true