mischief: tighten the detail page's live poll and drop dead state

The who-page poll now keeps the location line honest both ways (a mark
that came back to town stops showing its old expedition) and stops
polling once the adventurer leaves the board. Also collapses a redundant
branch in ResolveMischiefOrder and removes the always-false whoPage.Stale.
This commit is contained in:
prosolis
2026-07-14 23:12:01 -07:00
parent 16711e13e6
commit 4189c03a82
3 changed files with 18 additions and 11 deletions

View File

@@ -117,18 +117,17 @@ func ResolveMischiefOrder(guid, status, detail string) (MischiefOrder, error) {
return MischiefOrder{}, fmt.Errorf("mischief: bad verdict %q", status) return MischiefOrder{}, fmt.Errorf("mischief: bad verdict %q", status)
} }
now := nowUnix() now := nowUnix()
res, err := Get().Exec( if _, err := Get().Exec(
`UPDATE mischief_orders SET status = ?, detail = ?, updated_at = ? `UPDATE mischief_orders SET status = ?, detail = ?, updated_at = ?
WHERE guid = ? AND status = ?`, WHERE guid = ? AND status = ?`,
status, detail, now, guid, MischiefPending, status, detail, now, guid, MischiefPending,
) ); err != nil {
if err != nil {
return MischiefOrder{}, fmt.Errorf("mischief: resolve order: %w", err) return MischiefOrder{}, fmt.Errorf("mischief: resolve order: %w", err)
} }
if n, _ := res.RowsAffected(); n == 0 { // Whether the update moved a pending order or matched nothing (missing, or
// Either it doesn't exist or it's already terminal. Tell the caller which. // already terminal from an earlier verdict), the current row is the
return MischiefOrderByGUID(guid) // 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) return MischiefOrderByGUID(guid)
} }

View File

@@ -167,13 +167,17 @@
function txt(id, v) { var el = document.getElementById(id); if (el && v != null) el.textContent = v; } function txt(id, v) { var el = document.getElementById(id); if (el && v != null) el.textContent = v; }
var timer = null;
function refresh() { function refresh() {
fetch('/api/adventure/who/' + encodeURIComponent(token), { headers: { 'Accept': 'application/json' } }) fetch('/api/adventure/who/' + encodeURIComponent(token), { headers: { 'Accept': 'application/json' } })
.then(function (r) { return r.ok ? r.json() : null; }) .then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) { .then(function (data) {
if (!data) return; if (!data) return;
if (!data.live) { 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'); txt('who-where', 'Back in town');
if (timer) clearInterval(timer);
return; return;
} }
var d = data.detail || {}; var d = data.detail || {};
@@ -187,11 +191,17 @@
txt('who-threat', d.threat_level); txt('who-threat', d.threat_level);
setBar(); 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 */ }); .catch(function () { /* transient — next tick will do */ });
} }
setInterval(refresh, 60000); timer = setInterval(refresh, 60000);
})(); })();
</script> </script>
{{end}} {{end}}

View File

@@ -54,7 +54,6 @@ var abilityLabels = [6]string{"STR", "DEX", "CON", "INT", "WIS", "CHA"}
type whoPage struct { type whoPage struct {
pageData pageData
Mark RosterView Mark RosterView
Stale bool
HasDetail bool HasDetail bool
Detail whoDetail Detail whoDetail
Abilities []abilityRow Abilities []abilityRow
@@ -90,7 +89,6 @@ func (s *Server) handleAdventureWho(w http.ResponseWriter, r *http.Request) {
page := whoPage{ page := whoPage{
pageData: base, pageData: base,
Mark: toRosterView(entry), Mark: toRosterView(entry),
Stale: storage.RosterSnapshotAt() == 0,
} }
if d, abil, ok := decodeWhoDetail(entry.Detail); ok { if d, abil, ok := decodeWhoDetail(entry.Detail); ok {
page.HasDetail = true page.HasDetail = true