These commits are when the Protocol Buffers files have changed: (only the last 100 relevant commits are shown)
| Commit: | 43f3134 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Merge master into release-2026-10-30 Resolves pkg/mod/automod_test.go: master's version, plus the release's Redis pool for the stores, so a challenge can post its result to chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The documentation is generated from this commit.
| Commit: | 31c9e7b | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2151 from woogles-io/returned-tiles-chat keep challenge results in the chat
| Commit: | b0e17b7 | |
|---|---|---|
| Author: | César Del Solar | |
Add pinned announcements Pinned announcements are skipped by SetSingleAnnouncement, so the blog RSS updater never overwrites them. This allows keeping a blog post at the top of the front page above the auto-updated blog slot. If the feed's newest post is already pinned, the updater leaves things unchanged instead of showing it twice. The admin announcement editor gets a Pinned checkbox, and pinned announcements show a pin on the front page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 70aa1e6 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
let the planner preview and set a Team Round Robin's rounds per opponent A Rounds per opponent input, shown when planning two teams, previews the Team Round Robin with each pair playing that many rounds in a row, and Use this scheme saves it as the round controls' games per round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 71c1b16 | |
|---|---|---|
| Author: | César Del Solar | |
steer the planner away from schemes that repeat opponents while others go unplayed Count each player's distinct opponents, and rank a scheme down for every opponent someone could have met instead of a repeat, except when planning two teams, where playing only the other team is the point. Say so in the scheme's notes, and when the rounds run a few past full cycles, offer pre-pairing the cycles and letting COP pair the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 59e45e8 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
add a PreviewPairings endpoint for the pairing planner It pairs a throwaway copy of the division with every pre-paired scheme, through the division's own pairing code and seed, so the preview is exactly what the division would get. For each scheme it reports the pairings, firsts, byes, groups, how long a full cycle is, the spread of average opponent seeds, and how often the top seeds meet, with plain notes for the director. Schemes are ranked for what the director wants (a fair start before COP, the whole event, or two teams), and other numbers of rounds are suggested when they'd suit better. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 7869fe9 | |
|---|---|---|
| Author: | César Del Solar | |
retire the top vs bottom team round robin The team round robin between assigned teams replaces it. The round controls editor still lists it, so divisions that used it show it, but it can't be picked, and the new method is now just Team Round Robin. Divisions already using it still pair as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | a258da8 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
add a team round robin between assigned teams Players can be given a team, and the new assigned team round robin has the two teams play each other, keeping seed order within each team. Teammates never play: when the teams are different sizes, the bigger team's players sit out the difference, in a row. Firsts follow the schedule like the other team round robins, and every other cycle runs backwards with firsts swapped, so every player stays within one of even with no three in a row, and pairs who meet twice split their games. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 1164b1e | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
add snake pairing PR 1492 brought up to date with master: a snaked (boustrophedonic) round robin that splits the division into two teams by snake draft (seeds 1, 4, 5, 8, ... against 2, 3, 6, 7, ...), and a play-within-team option for it and the interleaved round robin. Snaked takes pairing method 14, since 11 and 12 went to COP and the Australian draw and 13 to win groups. play_within_team takes a new field, 22, rather than the reserved 11, which old saved round controls may still set as interleave_team_round_robin. Also a "Count firsts" director tool, which skips byes and shows seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | c24fecf | |
|---|---|---|
| Author: | Andy Kurnia | |
keep a challenge's result in the chat the page added a challenge's result to the chat by itself, "Challenged play was valid" or "Play was challenged off the board! Returned tiles: ...", so the line was gone after a reload or a channel switch. the server now stores the line, with no sender, in the game's chats for players and for observers, and sends it to whoever is in the game. the result event's new posted_to_chat says so, and the page then adds no line of its own; from a server that does not set it, the page still adds its own line. the returned tiles are listed in machine letter order, the blank first, rather than in each viewer's own tile order. a play that drew nothing reads just "Play was challenged off the board!". the gameplay tests' stores get a Redis pool from REDIS_URL, which CI sets, so a challenge there stores its line. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 7e94a73 | |
|---|---|---|
| Author: | Andy Kurnia | |
add a win groups pairing method to the proto WIN_GROUPS = 13 names a deterministic pairing method: players on the same wins form a group, and each group's top half plays its bottom half. the go and ts code is regenerated with the pinned generators. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 31a2e51 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2119: show the tournaments and clubs a user directs or entered show the tournaments and clubs a user directs or entered
| Commit: | 5a9e42f | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2118: find chats by id, page address or name, and tidy chat review [skip ci] find chats by id, page address or name, and tidy chat review
| Commit: | 6424257 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2116: offer WordSmog and ZOMGWords in the board editor [skip ci] offer WordSmog and ZOMGWords in the board editor
| Commit: | af472ef | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2121: name each profile game's league or tournament [skip ci] name each profile game's league or tournament
| Commit: | f765b5d | |
|---|---|---|
| Author: | Andy Kurnia | |
name each profile game's league or tournament a profile's game history and recent game cards did not say whether a game was a league, tournament or club game, nor which one. such a game now names its league, or its tournament, club or club session, linked to its page: an Event column in the table, and a line on the card, which has room for it (its icons keep to its bottom, and a long name ends in an ellipsis, which hovering gives whole). the recent games queries join the league's name and the tournament's name and slug, and GameInfoResponse carries them, so the page fetches nothing more. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 2d09a2f | |
|---|---|---|
| Author: | Andy Kurnia | |
list the tournaments and clubs a user entered See User Details now has "Tournaments and clubs they entered", listed on request: those the user played a game in, with how many games and when the last was, the last played first (a game that never started does not count); then the unfinished ones they are registered in. finding registrations reads the divisions of every unfinished tournament, hence the button. GetEnteredTournaments serves it, with the rule GetMyTournaments has: another user's list only for a caller who can see private user data. that rule's helper is now listedUser, shared by both, and the tournament type mapping is one function, competitionTType. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 65c1488 | |
|---|---|---|
| Author: | Andy Kurnia | |
show the tournaments and clubs a user directs nothing in the admin pages found tournaments or clubs by director. See User Details now lists the tournaments and clubs the user directs, newest first, each linked to its page, and one played in person says so: "Tournament (in person)". GetMyTournaments takes a username for this, as GetActiveCorrespondenceGames does: another user's list only for a caller who can see private user data. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 7f56bae | |
|---|---|---|
| Author: | Andy Kurnia | |
find a chat by id, page address or name the chat review's lookup took a game id or a full channel name, so a tournament's, club's or league's chat needed its channel name typed out, and none could be found by name. the lookup now asks the server. it takes a channel name as it is. otherwise what matches exactly comes first: a game, tournament, club or league by its id or its page's address, or the end of that address, such as a club's slug. the tournaments, clubs and leagues whose name or slug has the text always follow (the newest 20 tournaments and clubs, and the newest 20 leagues), and the table keeps that order for a lookup, while a search's rows still go newest first. what it finds is listed even with no chat yet, and a lookup that finds nothing says so. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | f3be56e | |
|---|---|---|
| Author: | Andy Kurnia | |
show the variant in annotated game lists the annotated game lists (the editor's and a profile's) gave only the lexicon, so a WordSmog or ZOMGWords game looked like a classic one. an annotated game now stores its variant, the lists get it, and a game that is not classic shows it after the lexicon, as the lobby's game lists do. games stored before this have none: one on the 21x21 board lists as ZOMGWords, the rest as classic. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 358924c | |
|---|---|---|
| Author: | Andy Kurnia | |
tell a league's page when its games change a league's page now joins the realm channel-league-<id>, and every move in one of the league's games, and every ending however it came (played out, time, resignation, forfeit, adjudication), sends LeagueGameUpdatedEvent there: the league, season, division, game, its two players, and whether it ended. an ending is sent after the standings take the result. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | e12cc88 | |
|---|---|---|
| Author: | César Del Solar | |
Merge pull request #2076 from woogles-io/league-opp-mi
| Commit: | 6a5decd | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2083: add a chat review tab for mods [skip ci] add a chat review tab for mods
| Commit: | c3369c2 | |
|---|---|---|
| Author: | César Del Solar | |
Merge pull request #2092 from woogles-io/multi-realtime-seeks [skip ci]
| Commit: | e6a60b0 | |
|---|---|---|
| Author: | César Del Solar | |
Merge pull request #2094 from woogles-io/profile-history [skip ci]
| Commit: | 833d037 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2102: Remove repeated user and puzzle lookups [skip ci] Remove repeated user and puzzle lookups
| Commit: | f208d09 | |
|---|---|---|
| Author: | César Del Solar | |
Check tournament recent-game censoring in one query Add user_id to TournamentGameEndedEvent.Player and fill it in RecentGames responses from the stored game info, so censoring can fetch every player's mod actions with one GetActionsBatch call instead of loading each user and their actions. Players without a stored ID still fall back to a lookup by username. Censored players' IDs are replaced with the censored name, as GameInfoResponse censoring does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 8d38896 | |
|---|---|---|
| Author: | Andy Kurnia | |
spell cross-table with a hyphen the panel label and the comments now say cross-table, as cross-tables.com writes it. identifiers keep their names, and the generated code changes only in the comments it copies from the proto and the query. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | ec806c4 | |
|---|---|---|
| Author: | Andy Kurnia | |
page game history by position as well as offset GetRecentGames can now page by position: the games just older than a game or a time, or just newer than a game or a time. each page walks the (player_id, updated_at) index from its starting point, ties on the same instant broken by game id, so it stays cheap however deep, where paging by offset has to step past every newer game first. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 41cb453 | |
|---|---|---|
| Author: | Andy Kurnia | |
accept an identical seek if the chosen is gone an accept now carries the other open seeks the client saw as identical (alternatives); if the chosen seek was canceled or taken in the meantime, the server takes the first alternative still there, checked as if accepted directly. so two players accepting one "N seeks" row both get a game while identical seeks are left, and a seek gone at claim time is reported as GAME_NO_LONGER_AVAILABLE, like one gone when read. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | c614c0f | |
|---|---|---|
| Author: | Andy Kurnia | |
accept an identical seek if the chosen is gone an accept now carries the other open seeks the client saw as identical (alternatives); if the chosen seek was canceled or taken in the meantime, the server takes the first alternative still there, checked as if accepted directly. so two players accepting one "N seeks" row both get a game while identical seeks are left, and a seek gone at claim time is reported as GAME_NO_LONGER_AVAILABLE, like one gone when read. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 36a9e06 | |
|---|---|---|
| Author: | Andy Kurnia | |
hide roster timeouts when the server sends none the roster's timeouts becomes optional, so a server that predates it is told apart from nobody timing out, and the #TO column and the clocks are left out. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 3461324 | |
|---|---|---|
| Author: | Andy Kurnia | |
hide roster timeouts when the server sends none the roster's timeouts becomes optional, so a server that predates it is told apart from nobody timing out, and the #TO column and the clocks are left out. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 8a43a1a | |
|---|---|---|
| Author: | Andy Kurnia | |
leave omiav out when the server sends none avg_opponent_mistake_index becomes optional and is set only for players with analyzed games, so the standings can tell "none" from 0 and drop the column when a server that predates it sends nothing. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 7869d9e | |
|---|---|---|
| Author: | Andy Kurnia | |
send when a game's clocks started Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 1f524c3 | |
|---|---|---|
| Author: | Andy Kurnia | |
send when the searched user last wrote Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 5bb3b2d | |
|---|---|---|
| Author: | Andy Kurnia | |
send when the searched user last wrote Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 8b34428 | |
|---|---|---|
| Author: | Andy Kurnia | |
send each roster season's timeouts Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | f689ec3 | |
|---|---|---|
| Author: | Andy Kurnia | |
decode chat channels for the chat review Each channel in the review now carries what it is about: a game's players with when the game was created and last updated, a private chat's two users, or a league's or club's name, each with the site path it belongs to. A game id or channel looked up directly is summarized by the server too, so it gets the same decoding. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 7e32393 | |
|---|---|---|
| Author: | Andy Kurnia | |
decode chat channels for the chat review Each channel in the review now carries what it is about: a game's players with when the game was created and last updated, a private chat's two users, or a league's or club's name, each with the site path it belongs to. A game id or channel looked up directly is summarized by the server too, so it gets the same decoding. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | cd68026 | |
|---|---|---|
| Author: | Andy Kurnia | |
add a division crosstable endpoint GetDivisionCrosstable returns every game of a division that is in progress or finished, not aborted or canceled: both players, their scores (running ones, from the history, while in progress), the winner and the player on turn. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | c2760bd | |
|---|---|---|
| Author: | Andy Kurnia | |
send opponent mistakes and tile progress Each game in GetPlayerSeasonGames now carries the opponent's mistake index, read from the opponent's slot like the player's own. Live games also carry the tiles each player has on the board, counted from the stored history with a phony's tiles taken back, and the tiles not yet played (bag and both racks: the distribution's tiles not on the board). Both come from data the call already loads. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | a5aafc3 | |
|---|---|---|
| Author: | Andy Kurnia | |
send each player's average opponent mistake index Season standings now carry avg_opponent_mistake_index: the mean of the opponents' mistake indexes over the same analyzed games that the player's own average counts. It is computed on read with one query per season, so there is no new column and nothing to keep in step. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 95a0b11 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
only offer unblock for players you blocked The block menu checked the combined both-directions exclusion list, so a player who had been blocked by someone was offered "Unblock" for them, which then failed. GetFullBlocks now also returns the subset of players we blocked, and the label is based on that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 90f4582 | |
|---|---|---|
| Author: | Andy Kurnia | |
list the chat of every game a searched user played A complaint may name the wrong game or opponent, and the complainant may have said nothing in the game that matters. So a username search now also reads the player and viewer chat of each game that user played whose chat may still be in Redis -- games in progress, and games that ended in the last 15 days -- and flags those channels user_played, whoever wrote in them. The scan skips them so none is listed twice. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 770a4af | |
|---|---|---|
| Author: | Andy Kurnia | |
let mods list and read the chat channels in redis ListChatChannels walks the chat streams with SCAN (never KEYS), a bounded number of steps per request, and summarizes each channel: the message count, the first and last times, and how many messages each sender wrote. It filters by channel type and by a user who spoke (or is a party to a private chat). GetChannelChats returns one channel's messages. Both need the moderator permission and only read. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | f03a5a9 | |
|---|---|---|
| Author: | jvc56 | |
address fifth review of lagged pairings PairRound clears a round before it pairs it, so when pairing failed the round was left empty. It now puts the round back the way it was: its pairings, and the COP gibsonization and standings that the failed attempt changed. A lagged round that a result can't pair keeps the pairings it had, and the response no longer disagrees with the server. Follow-on pairing failures are now reported to directors instead of only logged: as a TOURNAMENT_AUTO_PAIR_FAILED notice to every director when a result or player change triggered them, and as warnings in the response to PairRound, SetPairings and SetSingleRoundControls, which the director tools show. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | b64cea5 | |
|---|---|---|
| Author: | jvc56 | |
address second review of lagged pairings Results that PairRound fills in for byes and forfeits no longer pair or open rounds in the middle of a pairing: a flag suppresses the trigger while PairRound runs, and the trigger pairs rounds iteratively instead, checking the complete prefix again after each round and never pairing a round that has started. Drop the rule that paired any round with no pairings on every result, which undid pairings a director had deleted. A round whose lag changes is paired by SetSingleRoundControls instead, when it has no pairings and the round it is now paired from is complete, and the RPC reports whether that happened. In COP's simulations with pending rounds, the round being paired is now resolved against the standings before the pending rounds, which is where its pairings, the Factor 3 forced pairs and control loss's 1st come from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | d1aa401 | |
|---|---|---|
| Author: | jvc56 | |
add lagged pairings for tournament rounds A round can now be paired from the results through two rounds back (RoundControl.pairing_lag = 1), so it can be paired and opened while the round before it is still being played. Players finish their previous game before readying for a lagged round, AutoStart opens lagged rounds and announces them, and a lagged division finishes only once every round is complete. COP builds lagged requests like TSH's client does, and its simulations now play in-progress rounds with their real pairings, with byes as forced wins. Divisions without lag behave exactly as before, and stored tournaments need no migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 4928da2 | |
|---|---|---|
| Author: | Andy Kurnia | |
| Committer: | César Del Solar | |
show a user's correspondence games to mods the user lookup now has "Ongoing correspondence games": the opponent, the player on turn with their clock ticking toward a forfeit (the leagues' CorrespondenceTurnIndicator), and a link to the game, the closest to a forfeit first. for it, GetActiveCorrespondenceGames takes an optional username: empty or the caller's own name lists the caller's games as before; any other name needs can_see_private_user_data. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 2b2e0c5 | |
|---|---|---|
| Author: | Andy Kurnia | |
| Committer: | César Del Solar | |
show when a suspension ends in the user lookup the user lookup, the email search and the list of accounts on disposable-email domains now have a "Suspended" column: "permanently", "until" the end time, or blank, and a "Hide suspended accounts" switch hides those rows in both tables. the server fills UserDetailsResponse's new suspension field from the account's current SUSPEND_ACCOUNT action (not lifted, not yet over), with one query per search. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 1b3e4ad | |
|---|---|---|
| Author: | Andy Kurnia | |
show a user's correspondence games to mods the user lookup now has "Ongoing correspondence games": the opponent, the player on turn with their clock ticking toward a forfeit (the leagues' CorrespondenceTurnIndicator), and a link to the game, the closest to a forfeit first. for it, GetActiveCorrespondenceGames takes an optional username: empty or the caller's own name lists the caller's games as before; any other name needs can_see_private_user_data. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 7ea9182 | |
|---|---|---|
| Author: | Andy Kurnia | |
show when a suspension ends in the user lookup the user lookup, the email search and the list of accounts on disposable-email domains now have a "Suspended" column: "permanently", "until" the end time, or blank, and a "Hide suspended accounts" switch hides those rows in both tables. the server fills UserDetailsResponse's new suspension field from the account's current SUSPEND_ACCOUNT action (not lifted, not yet over), with one query per search. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 1cdd46d | |
|---|---|---|
| Author: | Andy Kurnia | |
list accounts on disposable email domains a new button lists every account whose email is on a known disposable-email domain, newest first, so they can be reviewed. each address's domain and its parent domains are joined against the list in one query. if the list cannot be fetched, the search says so instead of coming back empty. Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 9648b53 | |
|---|---|---|
| Author: | Andy Kurnia | |
mark disposable emails in the admin user lookup the user lookup and the email search now show whether each address is on a known disposable-email domain, checked against the list from the previous commit when a mod looks, so nothing is stored. when the list cannot be fetched, the column says "list unavailable" instead of staying blank, which would read as "no". Co-Authored-By: Kofi Bingo (Claude Opus 5.5, 1M context) <noreply@anthropic.com>
| Commit: | 3f68f35 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #2007 from woogles-io/client-attribution Record client address/identifier; end sessions on account suspension
| Commit: | abdd0bf | |
|---|---|---|
| Author: | César Del Solar | |
Show account status and connection history in the admin user lookup GetUserDetails now also returns verification, notoriety, bot flag, active moderator actions, the registration IP and client identifier, recent IP/client records, and other accounts sharing any of those. The admin page shows these below the lookup result; IPs link out to a geolocation lookup in the viewer's browser rather than being sent to a third party by the server. GetUserDetails and SearchEmail responses are now marked Cache-Control: private, no-store, since both are reachable by GET. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 46c969a | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #1991 from woogles-io/anno-time-penalty Time penalties in the board editor
| Commit: | 8271826 | |
|---|---|---|
| Author: | Jesse Day | |
| Committer: | Jesse Day | |
Let the annotator enter time penalties after the game ends
| Commit: | afac792 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Let directors rename a player in IRL tournaments For when someone drops out and another person takes their place. EditPlayer gains new_name. Edits (rename and rating) are now also allowed after a tournament has finished, so names can be fixed after the fact. Renaming is IRL-only: online, a player's ID is their Woogles account, so a rename would really be an account swap. In IRL mode the ID is token:name. Pairings, the matrix and ready states refer to players by index, so a rename only touches the player list, the index map and the stored standings. Names must be non-empty and unique across the tournament. Scorecard QR codes hold the shortest unique prefix of each player ID, and the token used to always be md5(name). A rename keeps the token instead, so the printed card keeps working for whoever takes over the spot and can't clash with anyone else's. Because of that, the token is no longer always md5(name): - A new player gets md5(name) unless a renamed player already holds it, in which case the name is salted until the token is free. Two players must never share a token: scorecard links and IRL score entry identify players by it. Only AddPlayers, RemovePlayers, MovePlayer and EditPlayer recomputed the token from the name; they now look IRL players up by name. Everything else (pairings, results, the QR page, the PDF generator, the tsh export) already read the stored ID. Existing tournaments keep working because their tokens are still md5(name). The score-entry page now says a link is invalid or ambiguous instead of throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | e56db9d | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Let directors rename a player in IRL tournaments For when someone drops out and another person takes their place. EditPlayer gains new_name. Edits (rename and rating) are now also allowed after a tournament has finished, so names can be fixed after the fact. Renaming is IRL-only: online, a player's ID is their Woogles account, so a rename would really be an account swap. In IRL mode the ID is md5(name):name. Pairings, the matrix and ready states refer to players by index, so a rename only touches the player list, the index map and the stored standings. Scorecard QR codes encode the shortest unique prefix of each player ID, so a rename always invalidates that player's printed code. In rare cases the new ID also starts with another player's printed prefix, which would make that player's card ambiguous. EditPlayer reports this so the director knows to hand out reprinted cards to everyone. The score-entry page now says the link is invalid or ambiguous instead of throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | a4201d9 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Copy gibsonization and spread cap when copying a tournament (#1994) The wizard's "copy from tournament" read TournamentDivisionSummary, which only carried the game request and round controls, and then sent hard-coded division controls. So gibsonize, gibson spread, minimum placement, bye cut-off, division spread cap and the suspended result/spread were all reset on every copy. The per-round spread cap override did survive, which is why it looked like everything else was copied. The summary now includes the division controls, and the wizard passes them through. It also returns round controls even when a division has no division controls. fixes #1994 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | 17a1a48 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Let directors edit a player's rating after adding them Adds an EditPlayer RPC and an "Edit player" director tool. Before the tournament starts, a rating change re-seeds the division the same way AddPlayers does (the shared code is now ClassicDivision.reseed). After it starts, only the rating changes; seeds stay fixed because past pairings and standings tiebreaks refer to them. The whole division is re-broadcast, since a re-seed can change every pairing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Commit: | e01c5c8 | |
|---|---|---|
| Author: | Jesse Day | |
| Committer: | Jesse Day | |
Let the annotator enter time penalties after the game ends
| Commit: | b581be2 | |
|---|---|---|
| Author: | Jesse Day | |
Let the annotator enter time penalties after the game ends
| Commit: | e852027 | |
|---|---|---|
| Author: | Jesse Day | |
Don't record auto-filled tiles as annotated event racks
| Commit: | 1dfb263 | |
|---|---|---|
| Author: | jvc56 | |
Reject AUTO pairing method once pairings or results exist verifyreq.Verify now returns INVALID_AUTO_PAIRING_METHOD_USAGE when PairMethod is PAIR_AUTO and DivisionPairings or DivisionResults is non-empty, since AUTO is meant to be used only at the very start of a tournament. Adds the corresponding PairError enum value and updates COP tests accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| Commit: | 7b540f2 | |
|---|---|---|
| Author: | César Del Solar | |
Bake badge metadata into the frontend bundle GetBadgesMetadata was 33% of all API traffic — ~23k calls/hour, roughly 30M/month. At CloudFront's per-request pricing that is about $31/mo to serve ~500 bytes of data that has changed a handful of times since the badges table was seeded in Feb 2025. The endpoint itself was not the problem. DisplayUserBadges calls useQuery(getBadgesMetadata) once per rendered username, before the badgeCodes guard, so even users with no badges fire a request. The QueryClient is constructed with no defaultOptions, which means staleTime is 0 and refetchOnMount is on, so every observer that mounts refetches. UsernameWithContext renders in 20+ places — every chat message, presence entry, lobby row and player card — so a single lobby page mounts 200-400 observers, and the multiplier is connected clients rather than page loads: one person joining the lobby costs one request on every client. Badge images are already compiled in (badge.tsx dynamically imports ../assets/badges/<code>.png), so adding a badge already required a frontend deploy. Shipping the descriptions the same way costs nothing extra and rides the existing content-hash cache busting — no CloudFront invalidation needed. - add cmd/gen-badges, which reads the badges table via the existing sqlc query and writes liwords-ui/src/gen/badges.ts - read BADGE_DESCRIPTIONS in badge.tsx and profile.tsx - drop the App.tsx prefetch, which was useless anyway since the cached value went stale the instant it landed - add badges.test.ts, asserting every badge code has a matching PNG and vice versa; nothing checked this before - mark GetBriefProfiles and GetBadgesMetadata NO_SIDE_EFFECTS. This is inert until useHttpGet is enabled on the transport, which is a separate change. The admin badge page keeps using the RPC, which remains the source of truth. Also corrects the badgesCacheTTL comment: it credited the hourly sub-badge-updater with changing badge definitions, but that task writes only to user_badges, never to badges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 1afd8a6 | |
|---|---|---|
| Author: | César Del Solar | |
Show how an analysis was run in the analyzer Add an info icon to the analysis summary bar whose hover card reports the engine version, when the analysis ran, which volunteer's worker ran it, how long it took, and how long it waited in the queue. The engine version was already reaching the client as GameAnalysisResult.analyzer_version; it was simply never rendered. The rest lives on analysis_jobs but was not exposed by GetAnalysisResult, so extend GetJobByGameID with claimed_at and a LEFT JOIN for the claiming user's username, and return the derived timings in a new AnalysisRunInfo message. Extending that query rather than adding a second one keeps GetAnalysisResult at a single round trip; the other four callers ignore the new columns. Every timestamp on analysis_jobs is nullable -- jobs predating worker tracking have no claimed_at -- so each derived value is reported as 0 rather than a bogus duration, and the card renders only the rows a job actually recorded. The requester is deliberately not shown. Requesters must be players in the game, so surfacing one would reveal which player asked for the analysis. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 4f7e3b0 | |
|---|---|---|
| Author: | César Del Solar | |
Show estimated endgame playout in the analyzer macondo v0.13.3 added EndgameMove.is_estimated, which marks moves past the endgame solver's search horizon. Beyond that point the solver plays the position out greedily, so the tail of a variation -- and the final spread it produces -- is a plausible continuation rather than a proven line. The analyzer rendered every move identically, so "best spread: +42" read as authoritative even when it came entirely from a playout. Vendor the new field for TS generation. No Go changes are needed: the vendored copy keeps macondo's go_package, so the backend already round-trips field 4 through upstream types. In the analyzer: - Insert a labeled dashed rule before the first estimated move. Estimated moves are always a contiguous tail, so one divider marks the boundary; when nothing was searched it lands at the top and reads as a caption. - Prefix the spread with a tilde-approx sign on both the PV header and each alternative line when that variation contains estimated moves. Estimated moves keep full contrast and stay clickable -- dimming and italics already mean "ignored" in the sim table. Numbering moved from <ol> to explicit N) so the divider can be a list item without consuming a counter, which renders identically since move_number is always i+1. The omgwords regeneration is an unrelated one-line comment catch-up left stale by the earlier SLV lexicon commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 572ebf4 | |
|---|---|---|
| Author: | César Del Solar | |
add slovene
| Commit: | b696f68 | |
|---|---|---|
| Author: | César Del Solar | |
Correct the stream_slot_count comment: deletion warns, re-pointing does not The comment claimed the UI warns before 'deleting or re-pointing' a slot somebody is streaming from. Only deletion warns. Re-pointing deliberately does not, and shouldn't: it happens to every slot every round, and it is not a hazard but the mechanism — stream slots mirror a broadcast slot precisely so directors can move it and everyone downstream follows. Deleting is rare and permanently breaks configured browser sources, which is why that one asks. (The confirm that does fire on reassign is pre-existing and unrelated: it warns when the slot's current game is still being annotated, and never consults stream_slot_count.) Comment only; no behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | f5e74a3 | |
|---|---|---|
| Author: | César Del Solar | |
Correct 'latest annotated game' wording in code comments too Same correction as the manual, applied to the source of truth: the proto enum, the migration, the query, and the OBS handler all described latest_annotation as following the most recently *edited* game. It orders by games.updated_at, which a move does not bump — SendGameEvent only upserts game_documents. So it is the most recently *started* game, and returning to an older one does not bring it back to the front. The note on GetLatestAnnotationForUser also records what fixing it would take, since game_documents has no timestamp column at all today. Comments only; no behaviour change. Generated files follow the proto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 464c91f | |
|---|---|---|
| Author: | César Del Solar | |
Add stream slots: permanent OBS URLs that outlive a broadcast An OBS browser source points at one URL forever, but a broadcast slot's URL embeds the broadcast slug, so it dies with the event. That breaks two real workflows: alternating one live stream between two concurrent events, and running a recurring stream that covers a different tournament every few weeks. Both currently mean editing every browser source in the scene. A stream slot is a user-owned pointer whose URL carries only the owner's username and the slot name: /api/annotations/obs/user/<you>/<slot>/<field> It resolves through a typed target, so one slot can follow different things over its life without the URL ever changing: none - renders the placeholder broadcast_slot - mirrors an event's slot, following whatever table that event features latest_annotation - follows the owner's most recently edited annotated game Pointing at a broadcast slot rather than straight at a table is what makes alternating work: each event's own directors keep their slot current while you are away, so cutting back lands on what that event features now. Keeping the target kind on the slot rather than in the URL is what lets someone stream club nights off their own annotations and later graduate to a broadcast without touching OBS. latest_annotation joins through broadcast_games, so when the game it lands on was claimed from a broadcast the tournament standings fields resolve too, and opponent_name works there since the tracked player is well defined. This replaces the old two-segment alias URL (/api/annotations/obs/user/<username>/<field>), which had no persistence and resolved to "whichever game this user last touched" - ambiguous once a broadcast has several annotators. Requests in the old shape now return an error naming the replacement, which renders inside the browser source itself. Also fixes a pre-existing frozen-overlay bug. The SSE pages only repaint on a message, and the broadcast callback stayed silent when a slot stopped resolving to a game - so reassigning a slot to an unclaimed table, or unclaiming a game, left the previous game on screen indefinitely while a fresh load of the same URL correctly showed the placeholder. Clearing is now an explicit push. Slot deletion also notifies, so those streams clear instead of freezing forever. Adds an in-app manual covering both audiences, opened from every place a slot is configured, and reorganizes the director panel into tabs. That panel had grown to four flat sections in one scroll, with the two most confusable concepts adjacent and near-identical, and ~80 lines of duplicated JSX between Annotators and Directors. Not yet exercised against a live database: the migration has not been applied and the end-to-end switching behavior is unverified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | b4127f5 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #1936 from woogles-io/new-tournament-irl-toggle tournament editor: IRL mode on create, slug link, and slug validation
| Commit: | 6a26de6 | |
|---|---|---|
| Author: | César Del Solar | |
Expose CreateDivision RPC for league admins ManualDivisionManager.CreateDivision already existed and is used internally by the season rebalancer, but there was no way to call it directly - only DeleteDivision and MovePlayerToDivision were wired up. Adds the RPC (mirroring DeleteDivision's shape and guards: requires can_manage_leagues, only allowed while the season is SCHEDULED) so admins can manually split an oversized division without going straight to the database. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| Commit: | b107dd0 | |
|---|---|---|
| Author: | Andy Kurnia | |
add mistake_index to PlayerSeasonGame Add an optional mistake_index field to the PlayerSeasonGame message so a player's per-game BestBot "mistake score" can be surfaced in the league game-history modal. It is optional (proto3 presence) so an unanalyzed game is distinguishable from a genuine 0 (a perfect game). Regenerated Go and TypeScript bindings. Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | 2c7d27d | |
|---|---|---|
| Author: | Andy Kurnia | |
add mistake_index to PlayerSeasonGame Add an optional mistake_index field to the PlayerSeasonGame message so a player's per-game BestBot "mistake score" can be surfaced in the league game-history modal. It is optional (proto3 presence) so an unanalyzed game is distinguishable from a genuine 0 (a perfect game). Regenerated Go and TypeScript bindings. Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | c3f5723 | |
|---|---|---|
| Author: | Andy Kurnia | |
add irl_mode field to NewTournamentRequest Allows the IRL (over-the-board) pairing mode to be set at tournament creation time instead of only afterward via SetTournamentMetadata. Regenerated Go and TypeScript bindings. Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | cf5f55c | |
|---|---|---|
| Author: | César Del Solar | |
puzzles: weekly PvP generation, tag info-leak fix, retry-without-spoiler - Split puzzle tags into category_tags (always sent) and descriptive_tags (only after a finalized attempt) to stop leaking solution shape via GetPuzzle before the user solves. - Add include_solution flag to PuzzleRequest so GetPuzzle no longer auto-reveals the answer on a previously-solved puzzle; the frontend now auto-enters retry mode instead of requiring a "Try Again" click. - Switch puzzle candidate queries to ORDER BY RANDOM() (with a matching functional index on games) for even weekly sampling, and drop the unused OFFSET parameter. - Loosen puzzle fetch transactions from RepeatableRead to the default ReadCommitted isolation, since no multi-statement snapshot was needed. - Add earliest_start_date to cap how far back PvP puzzle generation walks. - Add a weekly-puzzle-gen maintenance task with per-lexicon bucket/limit configs (English, French, German) mirroring historical one-off runs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| Commit: | 9fca8dd | |
|---|---|---|
| Author: | César Del Solar | |
league: add DeleteDivision RPC and admin UI for cleaning up empty divisions Exposes the existing ManualDivisionManager.DeleteEmptyDivision helper as an admin-only RPC (fails if the division still has players) so a division that was manually emptied via MovePlayerToDivision can be deleted and the remaining divisions renumbered, without a direct DB edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| Commit: | a240174 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #1888 from woogles-io/player-season-games-clocks show live clocks in the league player season-games modal
| Commit: | 3ac6f59 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
refactor: gate frontend UI on granular permissions instead of JWT role codes Replaces three uncoordinated permission systems (JWT short codes adm/mod/toc, per-component getSelfRoles checks, and getModList) with a single source of truth: the user's effective granular permission codes fetched via a new GetSelfPermissions RPC and stored in loginState.permissions. - Add GetUserPermissions SQL query and rbac.UserPermissions Go helper - Add GetSelfPermissions RPC to AuthorizationService (proto + handler) - Remove adm/mod/toc short-code mapping from socket JWT (perms claim retired) - Replace loginState.perms with loginState.permissions (Array<string>) - New hasPermission(permissions, Perm.*) helper with admin_all_access wildcard - Migrate all ~15 UI gating sites to granular permission checks; fixes Broadcast Creator role holders never seeing the New Broadcast button - Remove per-component getSelfRoles gating in leagues, tournaments, seek form - Guard /admin route client-side on AdminAllAccess permission Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| Commit: | 075c3d1 | |
|---|---|---|
| Author: | Andy Kurnia | |
return the live clock with in-progress league season games GetPlayerSeasonGames returned no timing for in-progress games, so a viewer of a player's season games could not see their live correspondence clocks. Add last_update, increment_secs and on_turn_time_bank_ms to PlayerSeasonGame, populated for in-progress games by loading each game's entity (a player has at most a handful of in-progress league games and this runs only on modal open, so the per-game load is fine). Whose turn it is is already encoded in the result field, so the client can name the on-turn player without an extra field. Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | c886ec0 | |
|---|---|---|
| Author: | Andy Kurnia | |
store the audraw reset_round 0-based to match the rest of the code reset_round was the lone 1-based round field; every other round (the RoundControls.Round, the RepeatRounds history, the pairing engine) is 0-based. Storing it 1-based forced a `-1` conversion and a `< 1` clamp in the dispatcher, and made the proto3 zero-default an invalid sentinel. Make reset_round 0-based: the dispatcher uses it directly as the round threshold, the proto3 zero-default (0) naturally means "avoid every prior meeting" (the strict default), and the clamp and conversion both go away. The frontend stores it directly too (no offset). No migration is needed: no production tournament has used the Australian Draw yet. Updates the proto/generated doc comments and the reset tests to the 0-based convention (the test reset_round values shift 9->8, 7->6, 1->0, describing identical thresholds, so every expectation is preserved). Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | 973b901 | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | GitHub | |
Merge pull request #1870 from woogles-io/australian-draw-be add the australian draw tournament pairing method
| Commit: | fade188 | |
|---|---|---|
| Author: | Andy Kurnia | |
add the australian draw tournament pairing method Adds the Australian Draw pairing method: a transparent, hand-playable, deterministic alternative to the matching-based methods. Round 1 is a casement draw (top half vs bottom half); every later round pairs the top remaining player with the highest-standing legal opponent that still leaves the rest of the field pairable, backtracking when a choice paints the draw into a corner, so a repeat-free draw is always found when one exists. A new 1-based reset_round round control (proto field 20) lets a director forgive meetings before a day boundary: a meeting in round r is avoided iff r >= reset_round (1 = avoid all prior, the default; >= current round = King-of-the-Hill). When a strict pass cannot pair the field the reset point is relaxed one round at a time, then errors cleanly if still impossible. An odd field is paired by padding it with a phantom bye at the bottom of the standings and running the even algorithm; whoever the phantom is paired with takes the real bye, unifying the odd and even cases with no special-casing. Backend and generated proto only; the director-UI changes are a separate follow-up PR. Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | 63fde47 | |
|---|---|---|
| Author: | Andy Kurnia | |
expose live time bank in active correspondence game lists The active-game payloads carried incrementSecs/lastUpdate/playerOnTurn but not the live consumed time bank, only the initial bank config. That left clients unable to compute the real time until a player times out (per-turn allowance + remaining bank), so league games -- which have banks -- could not be sorted or flagged correctly. Add a repeated int64 time_bank field to GameInfoResponse carrying the current per-player bank remaining in milliseconds, and populate it in all three active-correspondence builders in the game DB store. The two user-facing queries (ListActiveCorrespondenceGamesForUser and ...ForUserAndLeague) did not select the timers column, so add it there; ListActiveCorrespondenceGames already loaded it. Regenerated sqlc models and the buf proto bindings (Go + TS). Co-Authored-By: Kofi Bingo (Claude Opus 4.8, 1M context) <noreply@anthropic.com>
| Commit: | 66c0339 | |
|---|---|---|
| Author: | César Del Solar | |
feat: add RevokeUserFromLeagues RPC and UI for league promoters Adds a new can_revoke_from_leagues permission (migration 202606030001) assigned to League Promoter and Manager roles. Implements the RevokeUserFromLeagues RPC in the league service, and extends the invite widget with a revoke section. Also adds a League Administration link on the leagues list page for users with promoter/admin access. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 26f2995 | |
|---|---|---|
| Author: | César Del Solar | |
fix(broadcasts): OTel spans for RPC cache, OBS transparent default, editor nav, annotation_done, NATS user ID - Add OTel trace spans to cachedFetch (cache.name, cache.hit attributes) - Default OBS URL builder transparent background to on - Show "Back to broadcast" link in editor when game is part of a broadcast - Hide "Unclaim" button when annotation is already done (add annotation_done to GetBroadcastGameContextResponse) - Fix SendGameEvent publishing NATS activity on in-game player ID instead of annotator UUID Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 601b631 | |
|---|---|---|
| Author: | César Del Solar | |
feat(broadcasts): full game dashboard with archive, standings, highlights, and split feed endpoint Adds a complete broadcast viewer dashboard with tab navigation (Pairings, Standings, Live Now, Recently Completed, Archive, Highlights), per-game stats materialized into broadcast_games, and a separate GetBroadcastAllGames RPC for the Archive tab so the heavy feed-walk payload is only fetched when needed. Archive shows every game (annotated + TSH feed-only) with Went First / Went Second columns derived from p12 feed data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 9bc56cc | |
|---|---|---|
| Author: | jvc56 | |
Don't need to reserve since changes aren't in prod
| Commit: | e738d2f | |
|---|---|---|
| Author: | jvc56 | |
More AI review
| Commit: | 13f2dbe | |
|---|---|---|
| Author: | jvc56 | |
Expand Pairings API
| Commit: | cd41862 | |
|---|---|---|
| Author: | César Del Solar | |
Add FailJob RPC so volunteer workers can report corrupt games Add FailJob to AnalysisQueueService proto so macondo workers can immediately mark a job as permanently failed when they detect unrecoverable data corruption (e.g. a game with an 8-tile rack), instead of silently dying and letting the job waste its 3-attempt retry budget on other volunteers before ending with a generic timeout message. The existing unused FailJob SQL query (analysis_jobs table) is now wired up through the new connect-rpc handler in pkg/analysis/service.go. Auth is via X-Api-Key; the job must be claimed by the calling worker. Error messages are truncated to 1 KB and surfaced via GetAnalysisStatus (error_message field), which is already exposed to clients. Also regenerates all buf-generated Go/TS files (minor version drift). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 4b7bfbd | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
Copy division names, game settings, and round controls from source tournament When copying a tournament, the wizard now fetches per-division data (game request and round controls) via GetTournamentMetadata and applies them on creation. If all divisions share the same game settings they are copied as the global request; if they differ a per-division copy is stored and a note is shown to the user. Adds TournamentDivisionSummary to the proto and GetRoundControls() to the DivisionManager interface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 293e7bc | |
|---|---|---|
| Author: | Claude | |
| Committer: | César Del Solar | |
Add self-serve tournament creation with wizard UI - Add /tournaments route with step-by-step tournament creation wizard - Step 1: Choose IRL vs online mode, configure monitoring - Step 2: Set name, slug, schedule, description (markdown) - Step 3: Configure divisions and game settings (lexicon, time, etc.) - Step 4: Review and create - Add tournament copy feature: directors can copy settings from their past tournaments to quickly create similar events - Add rate limiting: max 3 tournament creations per week for regular Tournament Creator role; unlimited for Tournament Manager role - Add 'toc' permission to JWT for Tournament Creator/Manager roles so frontend can gate the creation UI - Add GetMyTournaments RPC to fetch tournaments where user is director - Add CountRecentTournamentsByUser and GetTournamentsByDirector to tournament store for rate limiting and copy features https://claude.ai/code/session_013uEnLRsznYmyUUbrTumdeX
| Commit: | ee9fbad | |
|---|---|---|
| Author: | César Del Solar | |
| Committer: | César Del Solar | |
improve broadcasts: polish, safeguards, renames, and sorting - Director panel: remove Active toggle, add section dividers - LIVE badge: hide for finished (scoresFinalized) annotations; show UPCOMING when pollStartTime is in future - Broadcasts list (/broadcasts): sort live first, upcoming next (soonest first), past archived at bottom (last 5); add GetAllBroadcasts RPC - Announcements widget: split broadcasts into live/upcoming (≤14 days) sections; show up to 3 past broadcasts - Back to Broadcast button in gameroom topbar for annotated games (mirrors Back to League/Tournament) - UnclaimGame: block unclaiming a game that already has a complete annotation - End-time validation in Create/Edit broadcast forms (end must be after start) - Rename omgwords "broadcast" terminology to "annotated": CreateAnnotatedGame, DeleteAnnotatedGame, AnnotatedGamesResponse, etc. - Broadcast slots: migration, SQL queries, proto messages, Go service, and UI wiring - OBS integration: obs.go and obs_handler.go stubs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | ef77286 | |
|---|---|---|
| Author: | César Del Solar | |
fix broadcast socket notifications and unclaim flow - Fix NATS topic prefix: channel-broadcast-{slug} → channel.broadcast-{slug} so messages match the socket server's channel.> subscription and actually reach viewers on the broadcast page - Add BROADCAST_GAMES_UPDATED (type 53) proto message and event, distinct from BROADCAST_UPDATED, for when annotation state changes (claim/unclaim) rather than feed data; frontend only invalidates GetBroadcastGames on receipt - Emit BROADCAST_GAMES_UPDATED from ClaimGame and UnclaimGame RPCs so viewers see annotation column update instantly without waiting for next poll - Replace "Delete this game" with "Unclaim this game" in the editor for games that are part of a broadcast; calls UnclaimGame and navigates back to the broadcast page on success Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| Commit: | 21d44b7 | |
|---|---|---|
| Author: | César Del Solar | |
add live broadcasts feature Introduces the full broadcast pipeline: TSH feed polling, game annotation claiming, and a React UI for viewers, annotators, and directors. - New `broadcasts` service with TSH/newt JSON feed parser, in-memory cache, background poller (respects poll_interval_seconds / start+end time window), and TriggerPoll RPC for immediate refresh - DB: broadcasts, broadcast_games, broadcast_annotators, broadcast_directors tables; migration 20260402000001 - RBAC: `can_create_broadcasts` permission + Broadcast Creator role - IPC: BroadcastUpdatedEvent proto + NATS publishing on each poll cycle - Frontend: BroadcastsList, BroadcastRoom (division/round selector, 30s poll, annotation column), CreateBroadcast, EditBroadcast, BroadcastAnnotatorPanel, BroadcastDirectorPanel; broadcast context (name/div/round/table) shown in GameInfo widget for /anno and /editor routes - gitignore .env and .env.production; add conn-db.sh utility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>