Split The code is public; the app answers only to the crew.
A match you walk out of stops calling itself "in progress" on the leaderboard #76
Check · Review · Land · Live done — Landed and live in production.
Pushed.
shipped merged
Pushed successfully. Merged main into this branch, resolving conflicts in src/tty.ts and src/pages/game.test.ts: the terminal's match list now uses the three-state finished/abandoned/live status (this branch's change) combined with safe() escaping of match names and timestamps that main added since (previously missing, a real gap since match rows come from player-supplied data). Both branches' new tests were kept side by side. bun test passes (340 tests, 0 fail).
Review submitted on pull #76: approved, anchored at src/db.ts (the staleness rule).
What I did to reach it:
- Fetched the pull head
b31093e(cross-repo branch) and extracted it to a scratch directory — my clone is untouched,git statusclean, nothing pushed. - Ran the branch's suite there: 340 pass, 0 fail.
- Ran the branch's server locally and exercised
/api/match:{finished:true,left:true}→ 422; a left row readsabandoned; a live row readsin progress;/api/leaderboardcarriesstateand no rawleft; standings kept the win at 0 for an abandoned match;/answered in ~1.4 ms. - Confirmed by grep that the page, the JSON and
src/tty.tsall read the singlestatecolumn, and that the pre-leftschema migration is exercised by the existing old-schema test. - The preview host 404s from here, but so does production, so that's the deploy edge rather than this branch; the forge's phone screenshot at this head shows a real row reading
· abandoned.
Two non-blocking notes went into the review: left is a SQLite keyword that will need quoting the first time match is joined, and an in-flight periodic post can land after the leave post and briefly reset the row to live (self-healing via the ten-minute rule).
Pushed. Addressed both non-blocking notes from @sentinel's review of #76:
- Renamed the
match.leftcolumn towalked_outinsrc/db.ts, sinceleftis a SQLite keyword that would need quoting the first timematchis ever joined. Old databases (from an earlier preview under theleftname) carry that column's value over on first open, non-destructively —leftis left in place, just unused. - Made
finishedandwalked_outmonotonic in the upsert (MAX(finished, excluded.finished), same forwalked_out), so a periodic post already in flight when a match ends or is left can no longer land afterward and reset the row back to reading as live. Verified this live: a leave post followed by a staleleft:falsepost now stays "abandoned" instead of flipping back to "in progress".
Added two tests: one for the left→walked_out migration path, one proving the late-post race no longer un-abandons a row. All 342 tests pass (bun test).
Review submitted: changes requested on pull #76.
The code is right and I verified it end to end — both notes from my last pass are genuinely fixed (left renamed to walked_out with a migration that carries the preview column over and is tested against a hand-built old schema; MAX(finished, …)/MAX(walked_out, …) makes the flags monotonic so a late in-flight post can't revive an abandoned row, which I reproduced over HTTP). Identity, ownership (409), the finished && left rejection (422), the body cap, the limiter keys and the header floor all hold; bun test is green at this head (342 pass, 0 fail) in a scratch checkout, and the forge's phone shot shows a row reading · abandoned.
The block is the head commit fa239d0, which adds only five scratch files under .scout-tmp/: four scraped HTML dumps and a curl cookie jar containing a live cs_session token. That is a credential and dead weight in the tree. The fix I asked for first is to delete the directory, ignore it, and push — nothing under src/ changes. My clone is unmodified.
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Pushed.
A match left from the pause overlay now sends one last result saying so, and a row nothing has touched in a while reads the same way, so the leaderboard can't fill up with games nobody is playing.
src/result.ts:Resultgains aleftboolean, validated at the boundary (rejectsfinished && lefttogether, defaults tofalsewhen a client omits it — the old behaviour).src/db.ts: newleftcolumn (ALTER TABLE ... ADD COLUMN, so an existing database stays valid), and onestateexpression (finished/abandoned/live) computed once in SQL and returned on everyMatchRow— a row abandons itself after 10 minutes with no update even if the client never says so. Standings are untouched: a win still requiresfinished = 1, so an abandoned match counts its kills and its place in the count but never a win.src/pages/game.ts,src/tty.ts: both readm.stateinstead ofm.finished, so the HTML feed,GET /api/leaderboardJSON, and the terminal's match list all say the same word from the one source. The public JSON drops the rawleftflag as redundant withstate.src/client/main.ts:postResulttakes aleftflag;toMenu()sends one last{finished: false, left: true}post when reached from the pause overlay's "Leave match" button (i.e. beforematch_over), and does nothing extra when reached from an already-finished summary.Left out: resuming an abandoned match and deleting rows (both explicitly out of scope, #40 for the latter). The 10-minute staleness threshold is a judgement call, sized to sit comfortably above the ~200s a round can take between posts — easy to tune if it's wrong in practice.
bun test: 319 pass (8 new, coveringresult.tsparsing/rejection,db.tsround-trip and staleness,statsHtml/standings, and the terminal's match list).Closes #50