Release Notes

Changelog

Every DynastyDesk release, newest first. Web dashboard, Discord bot, database, and OCR changes all in one place — the same notes posted to our Discord announcements channel.

Join Discord for Live Updates Setup Guide →

v1.2.0

Latest 2026-07-18

Social Studio — visual queue + post maker

  • webNew admin Social Studio (/admin/social): a visual dashboard for the evergreen social pipeline that used to require hand-editing JSON. It reads and writes calendar.json straight through the GitHub API, so the existing Mon/Wed Buffer drip keeps running untouched.
  • webQueue dashboard — upcoming posts in order (the next one badged Posts next), each with pillar, template/hero, platforms, an expandable caption preview, and one-tap edit / delete / move up-down / duplicate. Plus a recently-posted history and a weekly-digest panel showing which CHANGELOG versions the next Friday digest will draw from.
  • webPost maker — a form for title (auto-slugged id), pillar, template-card vs hero-image, body, hashtags, and platforms, with a live caption preview. Writes a clean entry to the queue; all commits carry [skip ci] so they never trigger a release.
  • webHero image upload — upload a PNG/JPG in the post maker and it commits to the marketing site's social/heroes/ and auto-fills the filename, so hero posts no longer need a manual file drop. Gated to app admins.

v1.1.4

2026-07-18

Fix — roster import dropped first names

  • webFixed the roster table showing no first names after a screenshot import, even though the import reported success. The import saved full first names to a dedicated Player.firstName field, but the roster page was still deriving the first-name column from the abbreviated name key and never read that field — so typed first names were saved but hidden. The table now reads and writes firstName directly. First names saved on earlier imports now appear retroactively, no re-import needed.
  • webYou no longer need to strip the leading initial off the last name in the import review step — the abbreviated name (e.g. R.Colton) stays as the stable identity key that re-uploads match on, and the full first name is stored alongside it.
  • webFixed roster import collapsing two different players who share a surname (e.g. E.Smith and J.Smith) into a single row. Import de-duplication now keys on name and position.

v1.1.3

2026-07-17

The Belt — reign history + Hall of Fame

  • webThe Dynasty Cup page now shows a Belt History section: a Hall of Fame (longest reign, most defenses, most reigns), a per-coach belt résumé (reigns, defenses, weeks held, longest reign), and the full title lineage. The current-holder banner now shows the active reign length and defenses.
  • botNew /cup belt history command — Hall of Fame plus the recent title lineage. /cup belt show now reports the current reign length (in advances) and defenses.
  • [bot/db] The Belt now tracks successful defenses — each advance where the holder wins their head-to-head game. Added BeltReign.defenses. Reign length is measured in advances (game weeks).

v1.1.2

2026-07-17

Dynasty Cup — settings polish + web point adjustments

  • webAdjust Cup points from the web. Commissioners now get an Adjust points panel on the Dynasty Cup page — pick a coach, enter points (positive or negative), and a reason that shows in their breakdown. Same as /cup adjust in Discord, no Discord required.
  • webCleaner Cup settings. Point values are now grouped (Game results / Accomplishments) with +/− steppers instead of cramped number fields, and enabling the Cup now surfaces it in the sidebar immediately.
  • webEasy Belt icon picker. The Belt settings now offer a row of one-tap emoji presets (no emoji keyboard needed on desktop), with a text field for a custom/pasted icon.
  • [web/db] Belt default icon changed from 🏆 to 👑 so it no longer looks like the Global Record Book contributor badge. Existing leagues on the old default were migrated.

v1.1.1

2026-07-17

Fix — Dynasty Cup settings page wouldn't load

  • webFixed the /settings page failing to load. The client-side settings form imported the Cup scoring module, which transitively pulled server-only database code into the browser bundle. The pure scoring constants now live in a separate cup-scoring module that's safe to import from client components.

v1.1.0

2026-07-17

Dynasty Cup + The Belt — a cross-season competition layer

  • webDynasty Cup. A new cross-season points ladder among coaches, at /cup. Points come automatically from user-vs-user wins (with ranked-win and rival-win bonuses), bowl/conference/national titles, awards, top recruiting classes, and players drafted — with a per-category breakdown. Win/loss resolution reuses the standings engine (forfeit → OCR box score → confirmed result), so the two always agree.
  • bot/cup command. /cup standings [year] shows the ladder; /cup adjust <user> <points> <description> lets commissioners grant or deduct points for anything untracked; the Cup top-5 is posted to the league channel on each advance.
  • webThe Belt. An optional dynasty-long championship belt one coach holds until beaten head-to-head. Configurable name, emoji, and seed mode (commissioner-assigned by default; also first-win, season-1 Cup leader, or random). The holder is badged with the belt emoji on the Cup page and in the Discord status board.
  • bot/cup belt show / give and automatic transfers: when the holder loses a UvU game the belt moves to the winner (reign history tracked); a "new champion" line posts on advance.
  • webCommissioner Settings page at /settings — the first per-league web config surface — hosts Dynasty Cup point values and Belt configuration. The Cup is hidden entirely for web-only members.
  • dbAdded League.cupEnabled/cupScoring and League.beltEnabled/beltName/beltEmoji/beltHolderId/beltSeedMode, plus new CupAdjustment (points ledger) and BeltReign (belt history) tables.

v1.0.8

2026-07-13

Stat import — game grouping, full opponent names, off-schedule warning

  • webMulti-game grouping is now order-independent. A game is grouped by its screens (one box score, one of each stat type), so a game whose box score you uploaded last — or not at all — no longer gets split into two. Previously anything before the box score became a phantom second "game."
  • ocrFull opponent names. Box scores now read multi-word team names in full — "Georgia Tech", "San Jose State", "Ole Miss" — instead of just the first word ("Georgia"). This also fixes the recorded game result showing the wrong opponent.
  • webOff-schedule heads-up. If a game's box score doesn't match the opponent scheduled for the week you picked, the review step now shows a non-blocking warning so you can double-check the week — you can still save.

v1.0.7

2026-07-13

Stat import — post-game screenshots taken after leaving the game now work

  • ocrFixed stat/box-score screenshots failing to import when taken from the stats screen you reach after exiting a game (the single-team "Player Stats" view, no TEAM column). Every screen — box score, passing, rushing, receiving, blocking, and both defense screens — was coming back as "No stat screens detected"; that whole layout had never been calibrated for CF27. It now reads and drops you into the same review-and-edit step as the immediately-after-game view. (The after-game view was unaffected and still works.)
  • ocrFixed two reader bugs that were dropping rows: player-stat tables were being cut off at their first row (a name longer than 8 characters was mistaken for the end of the table), and the highlighted top row's noisy scan could abort the whole table. Non-highlighted rows now read cleanly; the highlighted row and the odd misread digit are flagged low-confidence for you to fix in review, same as everywhere else.

v1.0.6

2026-07-13

Web — hide "vs. User" in solo web leagues

  • webFixed the schedule editor showing a vs. User option in single-user web leagues. Web-only members have no linked Discord ID, so the self-exclusion for the opponent list never matched them and left you in your own opponent list — now you're excluded by member identity, so solo leagues correctly show only vs. CPU while multi-user leagues are unaffected.

v1.0.5

2026-07-13

Roster screenshot import — accuracy overhaul, review step, offseason resync

  • ocrRoster screenshots now import your whole roster instead of dropping about half of it. The reader no longer throws out a player just because his OVR didn't scan — it keys on the name instead — so the highlighted top player of each screenshot and everyone in the 70s (which the CF27 font misreads) come through.
  • ocrQB now reads correctly instead of coming through blank (the game's "QB" was scanning as "0B").
  • webNew review & edit step before anything is saved: the parsed roster opens in an editable table so you can fix names, class, position, and OVR — with a "needs a look" count and a Show only flagged filter — then Import.
  • webLow-confidence flagging. Rows are highlighted when the OVR, position, or class didn't scan cleanly, when a name's initial looks off, or when a near-duplicate of another row is detected (an overlap between two screenshots).
  • webOVR auto-fill. Blank OVRs (the font's 7→1 misread) are reconstructed from the roster's high-to-low sort order and pre-filled in italics/yellow for you to confirm — cutting the numbers you have to type by roughly two-thirds.
  • [web][ocr] Archetype, Dev Trait, and full first name are now captured from the right-hand detail panel for the highlighted player on each screenshot, editable in the review table.
  • webSafer re-uploads. Re-uploading your roster (e.g. after Training Results or Position Change weeks) now matches players by name — including close OCR variants — and updates them in place instead of creating duplicates.
  • dbAdded Player.firstName (optional) to store the full first name read from the roster panel.

v1.0.4

2026-07-13

Web — matchups cleanup, unified stat import, roster sorting, smoother new-dynasty setup

  • webMatchups page now lists only weeks that actually have games — preseason and offseason weeks are gone — and no longer shows a duplicate Week 0 from a past season. The long row of week chips is replaced by a compact week dropdown with ‹ prev / next › arrows.
  • webImport Stats and Bulk Import are now one page. The uploader groups your screenshots into games automatically, so a single game and a whole week's worth work from the same place — no more choosing which import to use.
  • webRemoved the "College Football 27 — OCR may read incorrectly" banner now that CF27 calibration is done. Because stat screens read by layout ratios, Xbox screenshots work the same as PS5, with the Claude-vision fallback backing up tricky reads.
  • webSortable roster columns — click Name, Year, Position, Dev Trait, or OVR to sort your roster; click again to flip direction, once more to clear. Sorting never disturbs inline editing, and unsaved new players stay pinned to the bottom.
  • webStart a dynasty already in progress. The create-a-dynasty page now has an optional *"Joining a dynasty already in progress?"* section to pick your current year and week, so you don't have to manually advance to catch up.
  • webTeam logos in the new-dynasty picker sit on a light chip now, so dark navy/black logos are no longer hard to see against the card.
  • webThe offseason roadmap no longer appears on a brand-new dynasty's first Preseason — it shows once there's a completed season behind it.

v1.0.3

2026-07-11

Web — Games Played counts games, not stat lines

  • webFixed the GP column on Player Stats undercounting games. It was counting only the games in which a player recorded that specific stat, so a QB who threw in every game but ran in only one showed GP 2 on the Passing tab and GP 1 on Rushing. Games Played now reflects the games a player actually appeared in (across every stat category) and reads consistently on every tab and on the player detail page.
  • webNudged the slide-out Feedback tab lower down the right edge so it no longer overlaps the stats table and other page content.

v1.0.2

2026-07-10

Web — move games between weeks, drop Preseason from stat entry

  • webAdded a Move control on each row of your team's game log to re-tag a game to a different week — for fixing games logged against the wrong week.
  • webPreseason is no longer offered as a stat-entry week (it's an offseason/admin week with no games); game weeks now run Week 0 → National Championship.
  • webFixed solo (web-app) leagues being unable to save a screenshot upload to a not-yet-active week — the upload path now honors the same relaxed week rules as manual entry.

v1.0.1

2026-07-10

Web — stats fixes: week labels, per-game averages, solo week entry

  • webFixed week labels across the stats pages. Manual/upload week pickers and every game-log row now show the real week name (Preseason, Week 0, Week 1, …) instead of the raw internal week number, which was off by two.
  • webFixed per-game averages counting each screenshot as a separate game. A game submitted as several screenshots (passing / rushing / defense) is now counted once, so team and player per-game stats, the GP column, and record projections divide by the correct number of games.
  • webSolo (web-app) leagues can now enter stats for any week of the season, not just the active or completed ones — pick Week 0 during Preseason and log it, with a heads-up when the week isn't your active week yet. Discord leagues are unchanged (advancing stays the bot's job).
  • webOffensive line positions can now be split by side. LT/RT and LG/RG are selectable alongside the generic T/G on every roster and recruiting screen; screenshot imports stay generic and you opt into a side by editing the player.

v1.0.0

2026-07-10

1.0 — full College Football 27 support 🎉

  • webNew in-app feedback widget — a slide-out tab on every page lets any user report a bug or share an idea, with an optional screenshot, delivered straight to the team with their league and current page attached for context. No Discord account required.
  • webbotocrv1.0.0 marks complete College Football 27 support across DynastyDesk: post-game stats, box scores, rosters, the full CFB Records book, the Players Leaving / Draft card, and one-shot recruiting-class import all read CF27 — with an optional Claude-vision fallback for the game's trickiest fonts. It lands alongside the recent offseason roadmap & calendar, the coaching carousel, and dynasty eras. Thanks for playing.

v0.9.29

2026-07-10

OCR — LLM-vision fallback for low-confidence reads

  • ocrbotwebAdded an optional Claude-vision fallback. When Tesseract returns low-confidence fields on a post-game stat screen (or a box-score warning), that one screenshot is sent to Claude Haiku to re-read just those values from the image — directly targeting College Football 27's inherent font misreads (7↔1, 5↔0, dropped digits). It overwrites only the flagged fields; your review table stays the final gate. Entirely opt-in and off by default: it activates only when an ANTHROPIC_API_KEY is set and is a silent no-op otherwise (~$0.005/screen when enabled).

v0.9.28

2026-07-10

Web/OCR — recruiting-class import + OCR accuracy analytics

  • webocrNew Import Recruiting Class flow — upload a College Football 27 "Top Classes Details" screenshot and DynastyDesk reads the whole class (name, position, star rating, national & state ranks) into an editable review table, then adds every recruit to your HS board at once. Look for "Import class from screenshot" on the recruiting board.
  • webNew admin OCR Accuracy page (/admin/ocr) over the correction flywheel: most-corrected fields, top misreads (OCR → saved), and a confidence-threshold calibration readout showing how often ≥70%-confidence reads were still wrong.

v0.9.27

2026-07-10

Web — offseason roadmap redesign

  • webPolished the dashboard's offseason roadmap. A filled gold→blue progress track (plus a header progress bar and current-phase pill) replaces the thin connector line, numbered stepper markers show done / current / upcoming at a glance, action lists are now consistently left-aligned, and the type is larger and more legible. Completed steps and the progress fill pick up your team color when it's set.

v0.9.26

2026-07-10

Web — remove the unused Activity panel

  • webRemoved the "Recent Activity" panel from the dashboard, plus the empty Activity page and its sidebar link — nothing ever wrote to the audit log, so they were always empty. The schedule/roadmap now spans the full width. (The audit-log table is retained for possible future use.)

v0.9.25

2026-07-10

Bot/Web — coaching job change also clears recruiting board & portal

  • botwebWhen a coach takes a new job (applied at End of Season Recap), their recruiting board and transfer portal now archive along with the roster — both pages start empty at the new school. The Scouting Board is deliberately left intact.

v0.9.24

2026-07-10

Bot/Web/DB — offseason calendar & coaching carousel

  • botThe offseason is now a first-class stretch. During offseason weeks (End of Season Recap → Players Leaving → Portal → NSD → Training → Encourage Transfers) the bot no longer opens a weekly CPU-games thread, and the weekly board becomes an action checklist — the tasks for that week plus what's next — instead of a game scoreboard
  • botNew /offseason command prints the full between-seasons calendar (Conference Championships → Preseason) with each week's actions and a "you are here" marker
  • botNew /roster takejob — a coach (or a commish on their behalf) can accept a new job during bowl weeks; the move is staged and applied automatically at End of Season Recap
  • webThe dashboard now shows an offseason roadmap above the schedule during the between-seasons stretch, tracking progress week by week (tinted to your team's colors when available), with the full schedule collapsed by default but one click away
  • webNew Coaching Carousel card to accept a new job from the dashboard during bowl weeks, and a read-only Previous Schools section on your roster that keeps a former school's roster viewable after you move. The NFL Draft Board gains a School column so you can see every player you've sent to the pros and where they came from
  • dbAdded the "End of Season Recap" week to the season calendar (weeks renumbered 30 → 31). Added a per-player school snapshot, coach "pending job" fields, and a CoachTenure history so a coach who changes schools keeps old rosters/stats attributed to the right school while each school's records stay put

v0.9.23

2026-07-10

OCR — CF27 records, players-leaving card & recruit star ratings

  • ocrwebThe CFB Records import now works on College Football 27 (version-aware — CFB 26 records are unchanged). Recalibrated the whole table for CF27's restyled layout, and the Career / Season / Game level is now detected automatically and reliably from the highlighted tab — so scope and level are pre-filled for you across all nine national/conference/school × career/season/game views
  • ocrThe "Players Leaving" player card now reads correctly on CF27 (version-aware). The same card also appears on the Draft Results screen, so both are covered
  • ocrRecruiting "Top Classes Details" now reads each recruit's star rating (the graphical 1–5 stars), completing the recruiting-class table parser — the web import screen remains the one follow-up

v0.9.22

2026-07-09

OCR — CF27 roster + recruiting-class support

  • ocrRoster screenshots now read correctly on College Football 27 (version-aware — CFB 26 rosters are unchanged). Around 8–9 players parse per screenshot; the highlighted/selected row can occasionally need a manual fix and is covered by the existing de-duplication across multiple scrolled screenshots
  • ocrAdded a parser for CF27's "Top Classes Details" recruiting screen that reads the entire recruiting-class table — every recruit's name, position, offer, national & state rank, class, and state. (Star ratings, which are graphical, and the web import screen are follow-ups)

v0.9.21

2026-07-09

Web/DB — archived dynasties are read-only

  • dbwebArchived prior-era dynasties are now genuinely read-only. New data (stats, players, records, recruits, awards, matchups, etc.) can no longer be written into a past dynasty — enforced at the data layer for both the web app and the bot — season advance is blocked on archived leagues, and the app shows a clear "read-only" banner while you're viewing one. Browsing the old dynasty's history still works exactly as before
  • botFixed npm run bot:deploy failing with a Prisma "Invalid value undefined for datasource" error — the command now loads the repo-root .env no matter which directory it runs from

v0.9.20

2026-07-09

Bot/DB/Web — dynasty eras (start a new game without losing history)

  • botNew commish command /dd newdynasty — start a fresh dynasty era (e.g. moving from CFB 26 to CF27) on the same server. It archives the current league as read-only history, spins up a new one at week zero, and carries your crew over (Discord members kept, team picks cleared to re-draft). Player rosters, stats, records, and awards start empty for the new era; nothing from the old one is lost
  • botNew commish command /dd gameversion to set an existing league's game version (CFB 26 / CF27) directly
  • webThe league switcher now labels each entry by game version and flags archived (read-only) eras, and defaults you to the current era — so a server's past and present dynasties are easy to tell apart and browse
  • dbA Discord server can now hold multiple leagues over time (one active, prior eras archived): added active, archived, and previousLeagueId to leagues and lifted the one-league-per-server limit

v0.9.19

2026-07-09

OCR/DB — game-version awareness (CFB 26 + CF27 side by side)

  • dbAdded a gameVersion field to each league (defaults to CFB 26 for all existing leagues). Newly created leagues — via /dd setup or a solo web league — start on CFB 27
  • ocrStat and box-score templates now carry both CFB 26 and CF27 coordinate sets and pick the right one per league. This restores correct parsing for CFB 26 leagues (v0.9.18 had switched the coordinates over to CF27) while keeping CF27 leagues on the new layout
  • webbotScreenshot uploads (web multi/single upload and the Discord /submit) now read each league's game version and OCR accordingly
  • ocrFixed detection of CF27's "Players Leaving" screen (its title moved, so it had been falling through as an unrecognized screen)

v0.9.18

2026-07-09

OCR — College Football 27 support

  • ocrRecalibrated the screenshot pipeline for College Football 27. All seven post-game screens — box score, passing, rushing, receiving, blocking, and both defensive scrolls — are re-mapped to CF27's restyled layout (repositioned stat tables, the new on-screen TEAM column, added LONG columns, and the redesigned center-scoreboard box score) and verified against real 4K PS5 captures
  • ocrReworked screen detection for CF27: layout is now keyed off the TEAM column header, and the box score is positively identified by its "Score" label — fixing screens that previously failed to classify
  • ocrSharper digit reading on stat rows (higher-resolution crops tuned for CF27's textured row backgrounds) — recovers many values on the highlighted/selected row and both final scores on the box score. Remaining ambiguous reads are still flagged low-confidence for quick correction in the review table

v0.9.17

2026-07-09

Web — unsaved-changes warning on the roster

  • webEditing your roster (positions, OVR, dev traits, NFL draft designations, etc.) and then navigating away without saving now prompts a confirmation instead of silently discarding your changes. The warning covers in-app navigation — the sidebar, mobile drawer, header, and league switcher — as well as closing the tab or refreshing. It only appears when you actually have unsaved edits

v0.9.16

2026-07-09

Web/Bot/DB — NFL Draft & early departures

  • webYou can now mark your own players as leaving for the NFL and record the round they were drafted (Round 1–7 or UDFA) — a new NFL Draft column on your roster, with a → NFL tag on anyone who's declared. Works for seniors and underclassmen alike
  • webNew NFL Draft Board view (under Roster) listing every player from your program who left for the NFL, their round, draft year, and the class/position/OVR they left as. Drafted players stay on the board as alumni even after they've left your active roster
  • botwebSeason advance now removes drafted underclassmen from the roster instead of progressing their class — mirroring how seniors graduate. A drafted player is pulled at the next rollover regardless of class; undrafted players advance as before. Applied to both the Discord bot advance and the solo-league web advance so the two stay in sync
  • dbAdded draftedYear and draftRound fields to the player record to track early departures and draft results

v0.9.15

2026-07-08

Web — mobile drawer opens from the right

  • webThe mobile navigation drawer now slides out from the right to match the hamburger button's position (it previously opened from the left while the button sat top-right). Keeps the button and the drawer's motion on the same side and improves right-thumb reach

v0.9.14

2026-07-08

Web — PWA status-bar overlap fix

  • webFixed the mobile header and drawer colliding with the iPhone status bar (battery/wifi/clock) when the app is launched from the home screen as an installed PWA. The standalone web view fills the whole screen including under the status bar, so the top bar, drawer, and page content now pad themselves by the iOS safe-area insets (env(safe-area-inset-*)) to sit clear of it

v0.9.13

2026-07-08

Web — mobile experience + multi-game bulk stat upload

  • webThe dashboard is now navigable on mobile: a hamburger button in the top bar opens a slide-out drawer with the full navigation, league switcher, and sign-out. Previously the sidebar was hidden below md and phones had no way to move between pages. Applied to both the league and app-admin layouts (admin had no mobile chrome at all)
  • webNew Bulk Import flow (/stats/upload/bulk) — drop or select every screenshot from several games at once. Each shot is read individually with live per-file progress, results are auto-grouped into games (a box score starts a new game), defense screens are merged, and each game gets its own review card + week selector before a single Save All. Removes the need to run the single-game upload once per week when catching up a backlog
  • webBulk save is blocked with a clear warning if two games target the same week, since a week only holds one submission per user (prevents silently overwriting stats)
  • webInstallable as a PWA — added a web manifest, maskable icon, theme color, and iOS standalone metadata so the app can be added to the home screen and launched full-screen, making the phone-first upload workflow feel native
  • webMobile comfort polish: fixed content being clipped under the fixed mobile header, tighter small-screen padding, larger tap targets on editable stat cells, and horizontal scrolling for the dense team-stats table
  • ocrAdded a per-file OCR endpoint (/api/ocr/single) backing the bulk flow; the single-game upload path is unchanged

v0.9.12

2026-07-08

Web/Bot — favicon fix + status board polish

  • webFixed the browser tab icon defaulting to the generic favicon — the manual metadata.icons config pointed at /icon.png, which the Next app-icon convention doesn't actually serve there (it uses a hashed /icon URL), so the link 404'd. Removed the override and let Next auto-inject app/favicon.ico + app/icon.png correctly (hard-refresh to clear the cached default)
  • botStatus board no longer prefixes every ready/not-ready player with the section's emoji — the ✅ / ⏳ now live only on the section headers, cutting visual noise in the readiness list. Away members keep the 🏝️ marker since it distinguishes them within the combined ready count

v0.9.11

2026-07-08

Web — advance readiness view

  • webThe League page now shows an Advance Readiness card for the current week: the "all ready to advance" / "waiting on N" verdict, ready count, advance countdown, a forced-win-pending warning, and Ready / Not Ready / Away member lists — so anyone can answer "can we advance?" without opening Discord
  • webReadiness now mirrors the Discord status board exactly via a shared helper — away members count toward the ready threshold, and the commish dashboard's ready count was corrected to match (it previously undercounted away members)
  • botOCR worker and per-session image cache are now always torn down after a /stats submit (moved into finally), including on error paths — prevents a lingering Tesseract worker or cached decodes after a failed submission

v0.9.10

2026-07-05

Bot/Web — error tracking + uptime monitoring

  • botAdded Sentry error tracking — unhandled rejections, uncaught exceptions, login failures, and every slash-command/autocomplete error are now reported with command + user context. No-ops unless SENTRY_DSN is set
  • botScheduler now pings a Better Stack heartbeat (BOT_HEARTBEAT_URL) once per 15-minute tick so a crashed or hung cron loop pages instead of failing silently; each check in the tick is isolated so one failing check no longer aborts the rest of the tick
  • webAdded Sentry error tracking for Server Components, Route Handlers, Server Actions, and the browser (via instrumentation.ts / instrumentation-client.ts). No-ops unless SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSN are set
  • webNew /api/health endpoint that returns 200 only when Postgres is reachable, for an external uptime monitor

v0.9.9

2026-07-04

CI — auto-publish the website changelog page

  • ciauto-release.yml now checks out the ddwebsite repo, regenerates changelog.html from this file via a small Node script (scripts/build-changelog.js in that repo), and pushes it — so dynastydesk.gg/changelog.html stays in sync with every release automatically, with no manual step

v0.9.8

2026-07-04

CI — restore Discord release notifications

  • ciNo Discord release post since v0.9.1, silently: auto-release.yml creates releases with the default GITHUB_TOKEN, and GitHub doesn't let GITHUB_TOKEN-triggered events cascade into other workflows — so release-changelog.yml's release: published listener never fired for any release the bot created (v0.9.2–v0.9.7). auto-release.yml now posts to Discord itself right after creating the release, instead of depending on that cross-workflow trigger

v0.9.7

2026-07-04

Web/Bot — conference data fixes, realignment page polish

  • webbotCorrected default conference for 9 schools to match current real-world alignment (ESPN standings): Boise State, Colorado State, Fresno State, San Diego State, Texas State, and Utah State now default to Pac-12; Northern Illinois and UTEP now default to Mountain West; Louisiana Tech now defaults to Sun Belt. Existing leagues that haven't customized a school's conference pick up the corrected default automatically
  • webConference realignment page — school logo cards now sit on a solid white backdrop with a thicker team-color border instead of a low-opacity color tint, fixing poor contrast for teams whose color was hard to see against the tinted background
  • webConference realignment page — conference columns now size to their content (narrow when showing logos only, wider when "Show school names" is on) instead of a fixed grid, so more columns fit on screen at once when collapsed

v0.9.6

2026-07-04

Web — custom conference realignment

  • webNew commish-only "Conferences" admin page (/league/conferences) — drag schools between conference columns (logo cards with a toggle to show school names) and Save to realign your league's conferences for the current dynasty year. Conferences themselves stay fixed; only school membership changes, and only for your league
  • webA banner on the League page prompts the commish to review conference alignment during the preseason/offseason
  • webNew "Conference Record" card on the team stats page — season and career win-loss record against conference opponents, plus a "Conference History" list showing the record compiled in any prior conference. Career totals are scoped per conference, so moving conferences starts a fresh career tally in the new one without touching the old one
  • webRecord Book — conference record chasers now reflect a school's current (realigned) conference instead of its static default; records already set in a school's previous conference are untouched and stay attributed there
  • dbNew ConferenceAssignment table — sparse per-league, per-dynasty-year override of a school's conference, falling back to the static default when unset

v0.9.5

2026-07-03

Web — track a second league

  • webWeb users can now create and track a second (or third+) league from the dashboard. A "+ Add another league" link in the sidebar and a matching "Create a new league" card on the league picker (/select-league) route to the existing create-team wizard with a ?new=true flag that bypasses the "you already have a league" redirect. Switching between leagues already worked (cookie-based active league + league picker) — this just unlocks reaching the wizard a second time

v0.9.4

2026-07-03

Stats — manual entry team name fix, roster pre-population

  • webPOST /api/stats/manual — team name is now always derived server-side from the submitter's assigned roster team instead of trusting client input. A free-typed value like "App State" (vs. the canonical "Appalachian State") matched neither the school name nor abbreviation, silently excluding those stats from Record Watch even though they showed up fine in League Records
  • webManual stat entry — the "Your team" field is now a read-only display once your team is known (falls back to an editable field only if a member has no team assigned yet)
  • webManual stat entry — Passing/Rushing/Receiving/Defense tabs now pre-populate rows for players who already have a stat logged this season in that category, instead of starting from a single blank row every time
  • dbOne-off data correction: 3 existing GameStat rows with teamName: "App State" updated to the canonical "Appalachian State" so previously-submitted stats now show up in Record Watch

v0.9.3

2026-07-03

Schedule — solo-league fixes, active week indicator, cancel a game

  • webSchedule page — the "vs. User" / "vs. CPU" toggle is now hidden entirely for leagues with no other members (solo web leagues), so the Set Schedule form defaults straight to the CPU team picker instead of showing an unusable "vs. User" option with nobody to pick
  • webSchedule page — Set Schedule and Edit Opponent now update the row immediately on save instead of waiting on a full page refresh to reflect the change
  • webSchedule page — Preseason (Week 1) no longer appears as a row, since it never has game data. A persistent active-week label now shows next to the page title year-round (including every offseason phase — Portal, Signing Day, Training Results, etc.) so you always know where you are even on weeks with no schedule row
  • webSchedule page — added a "Delete" action on any scheduled game that hasn't had a result or stats logged yet, for fixing a mistaken entry or a week that should've been a bye. Blocked once a result, forced-win, self-reported completion, or any stats are attached to the game
  • webSchedule page — added a "?" help icon next to the Schedule heading with quick tips: leave bye weeks blank, use the FCS (generic) opponent for placeholder FCS games, use Delete to undo a mistake, and how CPU vs. UvU result confirmation differs
  • webDELETE /api/matchup/[id] — new endpoint backing the cancel action

v0.9.2

2026-07-03

Schedule — FCS placeholder opponent

  • webSchedule page — "vs. CPU" opponent picker (both the Set Schedule form and the Edit Opponent form) now includes an "FCS (generic)" placeholder option, for the games where CFB 25 subs in a generic FCS team instead of a real school. Renders with its own neutral badge instead of the unrecognized-team fallback styling

v0.9.1

2026-07-01

Stats — Dashboard "Add Stats" shortcut

  • webDashboard schedule — each game row now shows an "Add Stats" link under the week label when a matchup exists but no stats have been submitted yet. Clicking it opens manual entry with the correct week pre-selected, keeping score logging and stat entry in one place

v0.9.0

2026-06-27

Stats — Manual entry improvements + mid-dynasty setup

  • webManual stats entry — roster autocomplete: player names are now pre-populated from the active roster, filtered by position group per tab (QB for Passing, RB/FB/HB for Rushing, WR/TE/RB for Receiving, defensive positions for Defense). Free-text still allowed for players not on roster
  • webManual stats entry — optional box score section: away/home team, score, and rank fields. When submitted, auto-syncs a confirmed GameResult and marks the associated Matchup as complete, so the result feeds into the standings without a separate step
  • web/stats now redirects to /stats/players — the old landing page with an embedded upload widget is replaced by Import Stats and Enter Manually buttons directly on the Player Stats page header
  • webSidebar Stats section now lists "Import Stats" and "Enter Manually" as explicit sub-items alongside Team Stats and Player Stats
  • web/api/weeks — added weekNumber: { lte: 22 } guard to exclude offseason/placeholder weeks from the stats week picker
  • bot/dd setup — new starting_year option (integer, 2020–2060). Sets league.dynastyYear so leagues joining mid-dynasty don't have to manually advance through years. Defaults to the existing year on re-run (same safe-re-run behaviour as starting_week)
  • bot/dd setup confirmation message now always shows Year XXXX, Week Label so the commish can verify year and week at a glance
  • docsREADME — added "Starting Point Scenarios" section covering: fresh league (defaults), mid-season setup, mid-dynasty setup, and solo user joining a league already in progress

v0.8.0

2026-06-27

Awards — Trophy Case

  • dbNew Award model — stores season awards per league per dynasty year. Fields: category (individual / all_american / all_conference), awardType (named award e.g. heisman), tier (first / second / freshman), conference, playerName, playerPos, teamAbbr, teamName, submittedBy
  • web/awards — Trophy Case view page. Three sections: Individual Awards (14 named awards table), All-Americans (1st/2nd/Freshman tier tabs with position-ordered grid), All-Conference (conference chips + tier tabs). Year selector tabs for historical browsing; defaults to league.dynastyYear. Your team's winners are highlighted per section
  • web/awards/submit — user entry form. Pick category → pick award/tier → pick position → select from your roster dropdown (falls back to free-text if player isn't on roster). Year is auto-filled from the active dynasty year
  • web/awards/manage (commish only) — same form with full alphabetical team picker. Live table of current-season awards grouped by award type, with per-row delete
  • webGET /api/awards — returns awards for the league filtered by year (defaults to league.dynastyYear)
  • webPOST /api/awards — commish can set any team; users are server-locked to their own team. Year is always server-set from league.dynastyYear
  • webDELETE /api/awards/[id] — allowed for the commish or the original submitter
  • webRoster page — award badges appear inline next to player names: 🏆 for individual award winners, ⭐ (yellow) for All-Americans, ⭐ (purple) for All-Conference. Matched by player name; multiple badges shown if a player won multiple awards. Tooltips show the award name on hover
  • webSidebar — "Awards" nav item (Medal icon) with Trophy Case / Submit My Awards / Manage Awards sub-items. Gated behind awards feature flag (enabled)

v0.7.1

2026-06-26

Records — contributor badge + scope normalization

  • webContributor badge (🏆 amber trophy icon) on the Standings and League pages for users who have had a global record approved. Previously broke for non-Discord users because the badge lookup went through Member.discordUserId rather than the Account OAuth relation; fixed to query the Account table and match on userId so magic-link and Google OAuth users earn the badge correctly
  • webPOST /api/records/ocr — added "PAC12" to the CONFERENCE_ALIASES lookup table. Previously, OCR reads of "PAC12" (no dash) were not normalized and created a duplicate conference scope alongside the existing "PAC-12" records. Existing duplicate records in the database were migrated to the canonical "Pac-12" value

v0.7.0

2026-06-26

Non-Discord users — solo team management

  • dbMember.discordUserId is now nullable — web-created members are identified by userId (NextAuth User ID) rather than a Discord snowflake
  • dbMember.userId new field — FK to the NextAuth User table. New @@unique([leagueId, userId]) constraint for web members
  • dbLeague.guildId is now nullable — solo leagues have no Discord guild ID
  • dbLeague.createdVia new field — "discord" (default, all existing leagues) or "solo" (web-created)
  • webLogin page now offers three sign-in options: email magic link (password-free), Google OAuth, and Discord OAuth — all on one page
  • webNew /create-team page — two-step wizard to pick an FBS school and name a league, then creates a solo league (no Discord required). Accessible from the "no league" state after login
  • webAdvance Week button — commishes of solo leagues can advance the week directly from the dashboard. Full season rollover (player class progression, scouting board advances, SR graduation) runs identically to the Discord bot's advance path. Disabled with tooltip for Discord-managed leagues
  • webOnboarding checklist card — shown to new solo users on the dashboard: Team created, Add first player, Upload first game stat. Dismissible; auto-hides when all steps complete
  • webPOST /api/leagues/[leagueId]/advance — new API route for web-triggered advance. Solo leagues only; commish session required
  • webAll auth guards updated from discordId-required to user.id-required (NextAuth always populates this). Member lookups across all routes now use an OR clause: userId OR discordUserId

v0.6.6

2026-06-26

Records — My Record Watch page

  • web/my-records — new "Records" entry in the My Team sidebar section. Shows your team's active players chasing school, conference, and national records on a single page
  • webSeason tab displays a progress bar and on-pace season projection for your best player in each stat category, across all three scopes simultaneously. A ★ badge and "record broken!" label appear when a player has already surpassed the current mark
  • webGame and Career tabs show the current record holders for reference without live progress columns
  • webIf your school has no records imported yet, the School section is replaced by a CTA card linking to the Contribute Records page
  • webMembers with no team assigned see a gate message prompting their commissioner to set a team

v0.6.5

2026-06-26

Records — User-contributed school records

  • dbRecordSubmission model — stores user-submitted school record screenshots through an approval queue before entering the global record book. Fields: school (abbr), level, status (pending/approved/rejected), Discord submitter identity, rows (JSON), and optional adminNotes
  • web/records/submit — user-facing upload page for contributing school record screenshots. Scope is pre-locked to the user's assigned team; reuses the same OCR pipeline and editable review table as the admin import flow. Shows a warning if OCR detects a different school than expected. Users without a team assignment see a gate message
  • webSchoolRecordsBanner — shown on the Record Book page when a user navigates to their own school's scope. Displays per-level status (imported / pending / rejected) and a "Contribute records" CTA for any missing levels. Rejected submissions show the admin's note and allow re-submission
  • web/admin/records/submissions — admin queue page with Pending / Approved / Rejected tabs, sorted oldest-first. Each submission shows an inline editable review table (admin can correct OCR before approving), an "will overwrite existing" badge when global records already exist for that school+level, and Approve / Reject with note actions
  • webAdmin nav gains a "Submissions Queue" entry (Inbox icon) with a live pending-count badge
  • webSidebar nav gains "Contribute School Records" sub-item under Records (visible to all users when the records feature flag is enabled)
  • webGET /api/records/school-status — checks which levels have global records for a school and returns the requesting user's own pending/rejected submissions
  • webPOST /api/records/submissions — validates school ownership against member.teamId, enforces one pending submission per school+level globally (409 on duplicate), creates RecordSubmission
  • webPOST /api/admin/records/submissions/[id]/approve — approves with optional row edits; calls the shared importRecordRows() helper (same idempotent delete+reinsert path as the admin import endpoint)
  • webPOST /api/admin/records/submissions/[id]/reject — stores rejection note, sets status to rejected, allows the user to re-submit
  • webimportRecordRows() extracted to lib/records.ts — shared by the admin import route and the approval endpoint; eliminates duplication
  • webResultCard extracted to components/records/ResultCard.tsx — shared by the admin import page, admin submissions queue, and user submit page. Accepts readonlyScope prop (locks scope display) and scopeMismatch prop (shows warning when OCR detected a different school)

v0.6.4

2026-06-25

Fixed — Season and Game record OCR

  • ocrSeason and Game screenshots now parse all 9 values correctly. Previously, when the level tab text was unreadable, the parser defaulted to Career column layout and the year+value were concatenated into a single number (e.g. "2020623" instead of year=2020, value=623). Small values (1–2 digits) were also silently dropped when read from a narrow single-column crop.
  • ocrYear and stat value are now read as one combined region (same wide crop as the career stat column, extended right). Post-processing splits the result at character 4: the year always occupies the first four digits, the stat value follows. A single PSM 6 read on 5–9 digits of content is far more reliable than two separate narrow crops whose content varies from 1 to 5 digits.
  • ocrAutomatic Season vs Career detection: when the level tab text is unreadable, three probe reads on the year-column position determine the layout. If ≥2 of the first 3 rows contain a 4-digit value in the range 1950–2030, the screenshot is treated as Season/Game (career stat totals are always outside that range). The resolved level is returned in the API response so the review card pre-selects the correct tab.

v0.6.3

2026-06-24

Admin — Global Records import via screenshot

  • webAdmin Import Records page (/admin/records/import) — OCR-based bulk import of the global record baseline: select Season/Game/Career level, drop a CFB Records screenshot, review 9 parsed rows with editable fields and low-confidence warnings, then confirm to overwrite that scope+level globally
  • webAdmin sidebar gains "Import Records" nav link; Global Records page gains "Import screenshot →" shortcut button
  • webPOST /api/admin/records/import — app-admin-gated endpoint; idempotent delete+reinsert for the submitted scope+level, validated against known categories

v0.6.2

2026-06-24

Fixed

  • dbAdd cpuOpponentTeam column to Matchup — stores the CPU opponent team abbreviation at schedule-set time, so it's visible before a result is logged
  • [web] Schedule — CPU opponent team now saves when setting schedule — selecting a team from the CPU dropdown and clicking "Set Schedule" previously silently dropped the selection; it's now sent to the API and stored on the matchup
  • [web] Schedule — existing bot-created sim matchups now show "Set Opponent" / "Edit Opponent" — previously these rows had no interactive path to attach a CPU opponent team; they now expand an inline team picker that PATCHes the matchup. New PATCH /api/matchup/[id] endpoint added
  • [web] Schedule — Discord emoji stripped from member display names — platform usernames containing Discord custom emoji syntax (<:Name:id>) now render as plain text instead of leaking raw markup into opponent sub-labels

v0.6.1

2026-06-23

Records — global architecture, admin area, and redesigned navigation

  • dbRecord.leagueId is now nullable — null = global baseline shared across all leagues; league-specific overrides win via mergeRecords() helper
  • dbFeatureFlag model added — DB-backed feature flags replacing env-var allowlists; isFeatureEnabled("records") is now the single gate
  • web/admin/* area — gated by APP_ADMIN_DISCORD_IDS env var (distinct from commish role); sidebar shows "App Admin" section when active
  • webAdmin Global Records page (/admin/records) — CRUD for global baseline records (leagueId=null), inline row editing, scope navigation
  • webAdmin Feature Flags page (/admin/feature-flags) — toggle switches for all known flags; creates DB row on first toggle
  • webRecord book redesign — game-mirrored layout: Level tabs (Season / Game / Career) + 3-row scope navigation: National pill · Conference pills row · School dropdown row (defaults to user's own school)
  • webCommish "Manage Records" relabelled to "League Records"; import copy clarified as dynasty override only
  • dbSeed script updated: leagueId: null for all 108 global records; run with node packages/db/scripts/seed-records.js [--force]

v0.6.0

2026-06-22

Records module (feature-flagged — commish only via FEATURE_RECORDS_ALLOWLIST)

  • dbAdd Record model to schema: scope (national/conference/school), level (season/game/career), category (9 fixed types), holder name/school/year/value
  • webRecord Book page (/records) — tabbed by stat group (Passing/Rushing/Receiving/Defense), shows record holders with best-chaser progress bars and on-pace projections
  • webManage Records page (/records/manage) — commish CRUD: inline edit/delete, scope filters, add form with conditional conference/school fields
  • webImport Records page (/records/import) — OCR-based bulk import: select Season/Game/Career, drop a CFB Records screenshot, review 9 parsed rows, confirm to upsert
  • webDashboard widget — "Record Watch" card shows logged-in user's players chasing records with progress bars (≥30% threshold), linked to full record book
  • webManual game stat entry form (/stats/manual) — tabbed Passing/Rushing/Receiving/Defense form, dynamic player rows, submits directly to DB without OCR
  • webSidebar nav: Records section with Record Book / Import / Manage sub-links (Import + Manage are commish-only)
  • webFeature flag: FEATURE_RECORDS_ALLOWLIST env var (comma-separated Discord IDs); gates all Records pages, nav items, and dashboard widget
  • ocrNew recordBook template — detects CFB Records screen, extracts scope label + 9 fixed rows × 5 columns; exports parseRecordBook from OCR package
  • dbSeed script: packages/db/scripts/seed-records.js — pre-populates National, SEC, Big Ten, and Alabama school records from real NCAA history; run with DATABASE_URL=... node packages/db/scripts/seed-records.js [leagueId]

v0.5.6

2026-06-22

Fixed

  • [web] Matchups — upcoming weeks with queued games now visible — the week picker was filtered to current and complete weeks only, hiding any upcoming week that a commish had already queued matchups into via /matchup set. The query now also includes any week that has at least one matchup, so pre-set games appear immediately. Week buttons also now show the friendly schedule label (e.g. "Week 0") instead of the raw internal week number.
  • [web] HS Recruiting — pitch picker dropdown no longer clipped — dropdown was being cut off by the table's overflow container. Now uses position: fixed anchored via getBoundingClientRect so the full 5×4 grid is always visible regardless of scroll position or parent overflow.

v0.5.5

2026-06-22

Changed

  • [web] HS Recruiting — pitch picker as dropdown — pitch picker is now a compact select-style trigger that shows the chosen pitch icon + name. Clicking opens the 5×4 icon grid as a popover; selecting closes it immediately. No longer takes up a full row when a pitch is already set.

v0.5.4

2026-06-22

Changed

  • [web] HS Recruiting — pitch picker — "Correct Pitch" is now a 5×4 icon grid matching the game's 20 recruiting pitches (College Experience, Team Player, Campus Personality, Gamer, Standard Bearer, Student of the Game, Hometown Hero, Status Seeker, The Clutch, Primetime Player, Coach Connection, Aspirational Goals, To The House, Football Influencer, Clocked In, Star Search, Grassroots, Conference Legend, Sunday Player, Gym Rat). Replaces the old Yes/No boolean toggle. Schema column changed from boolean to string to store the pitch name.

v0.5.3

2026-06-22

Fixed

  • [ocr] Dev trait star icon bleed — shifted devTrait crop x from 0.893 → 0.903 to skip the star icon glyph that was still bleeding into the OCR region and producing garbled reads.

v0.5.2

2026-06-20

Fixed

  • [ocr] Player card preprocessing — inverted (white-on-dark) regions now invert *before* normalize() so histogram stretching works correctly for dark-text-on-light output. Scale increased 2x → 3x and contrast 0.3 → 0.6 for sharper binarization on the condensed game font. Region coordinates for OVR, name, position, archetype, class, and dev trait all widened/tightened for better coverage.

v0.5.1

2026-06-20

Added

  • [ocr] Players Leaving screen parser — new playersLeaving.js template scans the Week 23 "Players Leaving" table (name, position, year/redshirt, OVR, reason). Uses readText per-cell to preserve text fields. Classifier detects the screen by its header region; parseImage dispatches to parsePlayersLeaving.
  • [web] Portal OCR import/api/ocr/portal-out endpoint accepts one or more Players Leaving screenshots and returns parsed rows. The Transfer Portal "My Players" tab now has an "Import from screenshot" button that opens a two-step modal: upload → review table with inline editing, checkboxes to exclude rows, and graduation entries greyed out automatically. Confirmed rows are posted to /api/portal (direction="out") and appear in the list immediately.

v0.5.0

2026-06-21

Added

  • [web] Recruiting module — new "Recruiting" section under My Team sidebar with three sub-sections:
  • HS Board (/recruiting/hs) — track high school recruits with star rating, grade (Gem/Normal/Bust), position, archetype, dev trait, class, OVR rank, state rank, correct pitch, and status (Targeting → Offered → Committed → Signed → Decommitted). Status changes are immediate; all other fields are inline-editable via expandable rows. Signed recruits surface an "Add to Roster" button once Week 29 (post-NSD) is reached.
  • Transfer Portal (/recruiting/portal) — tabbed Incoming / My Players view. Incoming players track fromSchool and status (Available → Committed → Signed Elsewhere → Withdrawn). Outgoing (your own) players track portal outcome (In Portal → Returned / Signed Elsewhere / Unsigned).
  • Scouting Board (/recruiting/scouting) — long-lived big board for recruits who signed elsewhere but may enter the portal in future seasons. Class (FR→SO→JR→SR) advances automatically each season rollover; SR entries graduate (deactivated). "Move to Transfer Portal" promotes a player directly to the incoming portal list.
  • [db] Recruit, ScoutingEntry, PortalEntry models — three new tables pushed to Railway covering all recruiting data. Recruit and PortalEntry are scoped to a dynasty year; ScoutingEntry is year-agnostic and updates in place via class progression.
  • [bot] Scouting board class progression — season rollover in scheduler.js now advances currentClass on all active ScoutingEntry records (FR→SO→JR→SR) and deactivates graduated SR players.
  • [web] Global input-cell / select-cell CSS utilities — reusable form-field classes added to globals.css.

v0.4.6

2026-06-19

Added

  • [web] Neutral site location — "Neutral" option added alongside Home / Away / Unknown in the Set Schedule and Log Result forms. Both UvU players see a yellow "N" badge on their schedule row. neutralSite stored as a boolean on Matchup.
  • [db] Matchup.neutralSiteBoolean @default(false) column pushed to Railway.
  • [bot] /result log → thread post — after logging a result, the bot now posts a summary message to the matchup thread (if one exists). CPU games post immediately on confirmation; UvU games post "pending confirmation" with the opponent @-mentioned.
  • [web] Favicon — replaced the default Next.js favicon with the DynastyDesk logo via app/icon.png.

v0.4.5

2026-06-19

Added

  • [web] Set Schedule — empty-week rows now show a "+" placeholder with a "Set Schedule" inline form. Users can pick UvU vs. CPU, select the opponent from a dropdown, and mark themselves Home or Away. For past weeks, a "Log Result" checkbox lets you submit scores in the same step.
  • [web] Home/Away tracking — new homeUserId field on Matchup (DB migration applied). Schedule rows display an H or @ badge. The Log Result form also exposes a Home / Away / Unknown selector that updates the matchup record.
  • [db] Matchup.homeUserId — nullable Discord user ID indicating which player is playing at home.

Changed

  • [web] Team badge colors — all 138 teams now use real primary brand hex colors (e.g. Ohio State scarlet, LSU purple, Tennessee orange) instead of hash-derived hues. Sac State green badge; NDSU dark green badge.
  • [web] NDSU + Sacramento State enabled — both teams promoted from future_2026 to current status.
  • [web] Nav order — sidebar sections reordered to My Team → League → League Ops.
  • [web] Dashboard header — page title changed from "Week X" to "{year} Schedule" (e.g. "2026 Schedule"). The active week is already visually indicated by the NOW badge on the schedule itself.
  • [web] Preseason hidden during season — the Preseason row group is suppressed once any regular-season week has started.
  • [web] Log Result form — includes Home / Away / Unknown selector; updates homeUserId on the matchup when saving.

v0.4.4

2026-06-19

Changed

  • [web] Sidebar nav restructured — new top-level "League" section (Matchups, Standings) visible to all users. "My Team" section now contains only personal items (Dashboard, Roster, Stats). Commish-only items (League admin, Activity) moved to a "League Ops" section at the bottom.
  • [web] Dashboard layout — weekly matchups panel removed from the commish dashboard; schedule now occupies the full 2/3 column for both commish and regular users. Matchups are accessible via the League nav.
  • [web] Schedule redesign — each week is now a distinct card with a 48×48 team badge (deterministic per-team color derived from abbreviation), large monospace score, and a prominent colored W/L badge. School name shown as the primary opponent label; player name as subtitle. Ranked matchups display inline below the opponent.
  • [web] Typography--muted-foreground bumped from L=0.75 to L=0.82 for better readability of secondary text on dark backgrounds.

v0.4.3

2026-06-18

Fixed

  • [web] Commish dashboard missing schedule — commishes saw an early-return view with only the current-week matchup list and no personal schedule. Schedule data is now fetched before the commish/user split, and ScheduleTable is rendered at the bottom of the commish dashboard too.

Changed

  • [web] Stats sub-nav in sidebar — the Stats nav item now expands to show "Team Stats" and "Player Stats" as indented sub-links whenever any /stats/* route is active. Direct access without going through the hub page.

v0.4.2

2026-06-18

Changed

  • [ocr] Image cache per OCR session — each uploaded image is now decoded from disk once per submission and reused for all region crops. Previously Jimp.read was called once per region (~50–70× per image), making full 4K PNG decoding the dominant CPU cost per request.
  • [ocr] Persistent Tesseract worker — the worker now stays alive between requests and auto-terminates after 5 minutes of inactivity. Previously it was torn down after every OCR request, paying the full language-model cold-start cost on each submission.

v0.4.1

2026-06-18

Added

  • [db] OcrCorrection model — stores per-field diffs between raw Tesseract output and user-saved values on every stat submission. Captures screen type, field name, OCR value, corrected value, and confidence score.
  • [web] OCR correction logging — raw OCR output is now snapshotted before the review/edit step and diffed against the final saved values. Corrections are written non-fatally after each submission as groundwork for future OCR accuracy analysis and pattern detection.

v0.4.0

2026-06-18

Added

Schedule System

  • [web] Season schedule on dashboard — user dashboard now shows a full season schedule (weeks 1–22) replacing the single matchup card. Preseason and Regular Season are expanded by default; Postseason (weeks 18–22) is collapsed until a user has activity there. Offseason weeks (23–30) are hidden entirely.
  • [web] Year selector — schedule header includes a season dropdown when multiple dynasty years exist, allowing users to browse past seasons read-only.
  • [bot] /result log — new slash command to record post-game results. Options: score, opponent_score (required), plus optional opponent_team, my_rank, opponent_rank, week. CPU/sim games auto-confirm; UvU games send a Discord DM to the opponent.
  • [bot] /result view — shows the logged result for the current (or specified) week.
  • [bot] UvU result confirmation — when a player logs a UvU result, the opposing player receives a Discord DM with ✅ Confirm / ❌ Dispute buttons. Confirming marks the matchup complete; disputing alerts the commish.
  • [web] Inline result logging — past weeks with no result show a "Log Result" button that expands an inline form. UvU games prompt the opponent to confirm or dispute directly in the schedule table.
  • [web] /api/result POST — creates or updates a GameResult record. CPU games confirm immediately; UvU games go pending and trigger a bot DM via the scheduler polling loop.
  • [web] /api/result/[matchupId] PUT — confirm or dispute a pending result. Validates caller is the opposing player.

Score & Result Tracking

  • [db] GameResult model — stores userScore, opponentScore, opponentTeam, myRank, opponentRank, result (W/L/T), status (pending/confirmed/disputed), submittedBy, confirmedBy, and notificationSent flag for bot DM dispatch.
  • [bot] /games score display — matchup embed now shows confirmed results (e.g. W 42–17 (#8 vs #15)) and pending confirmation state (🕐 Awaiting confirmation).
  • [bot] Status board scores — completed UvU games on the status board now show the score inline (e.g. ✅ 42–17).
  • [bot/web] OCR → GameResult sync — submitting box score screenshots (web Submit page or bot /stats submit) automatically creates a confirmed GameResult record. Manual /result log entries are skipped if a confirmed OCR result already exists.
  • [web] Standings fallbackcomputeStandings() now falls back to confirmed GameResult records when no BoxScore exists for a completed matchup.

Multi-Season Support

  • [db] Week.dynastyYear — weeks are now scoped to a dynasty year. Unique constraint changed from (leagueId, weekNumber) to (leagueId, weekNumber, dynastyYear), fixing a latent bug that would have broken week creation in season 2.

Changed

  • [bot] Thread creation message — references updated from /matchup complete to /result log.
  • [bot] Game reminder DM — updated to direct players to use /result log to record their score after the game, then react ✅ to ready up.
  • [bot] /help — replaced /matchup complete with /result log and /result view in the quick reference.

v0.3.1

2026-06-18

Fixed

  • [web] Position dropdown — updated to game-specific positions: QB, HB, FB, WR, TE, T, G, C, REDG, LEDG, DT, SAM, WILL, MIKE, CB, FS, SS, K, P. Previous list used generic labels (RB, OL, DL, LB, DB) that didn't match in-game values.
  • [web] Archetype field — changed from free-text input to a position-dependent dropdown. Valid archetypes populate automatically when a position is selected; changing position clears archetype if the current value is no longer valid. Locked with placeholder text until a position is set.
  • [web] Dev trait dropdown — corrected values to Normal, Impact, Star, Elite.
  • [web] OVR input — replaced <input type="number"> (which showed unwanted spinner arrows) with <input type="text" inputMode="numeric">. Accepts up to 2 digits; non-numeric input is stripped.
  • [web] Roster table empty after upload — added router.refresh() on successful OCR import so the player table repopulates immediately without a manual page refresh.
  • [ocr] Position normalization — OCR import now maps game-native position codes to canonical values before saving: LT/RT→T, LG/RG→G, RE→REDG, LE→LEDG, MLB→MIKE, LOLB→WILL, ROLB→SAM. Previously only QB and TE imported correctly; all other positions stored the raw game value.

v0.3.0

2026-06-18

Added

Web Dashboard

  • [web] Role-aware dashboard — commishes see all matchups, ready counts, and recent activity; regular users see their personal matchup card, last game box score + leaders, and enrichment status.
  • [web] Roster page — each user's player table with inline editing for pos, OVR, archetype, dev trait, year, and redshirt flag. Incomplete players (missing pos or OVR) sorted to top with a yellow indicator.
  • [web] Roster screenshot upload — drag-and-drop modal on the Roster page accepts PS5 4K screenshots and imports up to 10 players per image via OCR. Multiple screenshots can be uploaded in one batch; duplicates are deduplicated by name.
  • [web] Stats submit drag-and-drop — the Submit page now accepts screenshots via drag-and-drop in addition to the file picker.
  • [web] Role-split sidebar — League Ops section (Matchups, League, Activity) is visible to commishes only; My Team section (Dashboard, Roster, Standings, Stats, Submit) is shown to all members.
  • [web] League Ops → League page — lists all active league members (renamed from "Roster").

Player System

  • [db] PlayerSeason modelPlayer is now identity-only (name, jersey, active). All season-specific data (pos, OVR, archetype, devTrait, year, redshirt, isRedshirtYear) lives in PlayerSeason keyed by (playerId, dynastyYear).
  • [db] League.dynastyYear — tracks the current in-game season year (default 2026). Increments automatically on season rollover.
  • [web] Player enrichment — stats submission (web + bot) creates PlayerSeason stubs for the current dynasty year; users fill in pos/OVR/archetype/devTrait on the Roster page.
  • [web] Redshirt tracking — users can toggle "RS?" on any player mid-season via the Roster page. At season rollover, redshirted players stay at their current class; the isRedshirtYear flag clears and redshirt is permanently marked.

OCR — Roster Screen

  • [ocr] Roster template — new OCR template for the in-game Roster screen. Calibrated against 3840×2160 PS5 screenshots: startY=0.430, rowHeight=0.045, stripH=0.018. Reads name, year (including RS suffix), position, and OVR for up to 15 rows per screenshot.
  • [ocr] Roster classification — classifier updated to detect "ROSTERS" header at { x:0.040, y:0.020, w:0.200, h:0.050 } and route to parseRoster().
  • [ocr] parseYear resilience — handles common Tesseract misreads: 5→S, 0→O, RY→RS. Correctly parses "50 RY" as SO with redshirt.

Bot

  • [bot] Season rollover logicperformAdvance() now detects week-30 advance and runs class progression: FR→SO→JR→SR, RS-flagged players stay same class, SR players without RS flag are marked inactive (graduated). OVR clears for the new season; pos/archetype/devTrait carry forward.

Changed

  • [web] Navigation renamed — "My Players" → "Roster"; old "Roster" (league members) → "League".
  • [web] Font — base font size bumped to 18px. Section labels in the sidebar are more prominent with accent color + gradient rule.
  • [web] Sidebar widerw-60w-64.

Schema changes

All schema changes were applied to Railway via db:push during development. No additional migration needed on deploy.

  • Added League.dynastyYear Int @default(2026)
  • Added PlayerSeason model (see above)
  • Removed flat Player.position, Player.archetype, Player.devTrait, Player.ovr — replaced by PlayerSeason

Deploy notes

  • No npm run deploy needed — no new slash commands in this release.
  • git push → Railway auto-deploys both the web service (Dockerfile) and bot service.

v0.2.7

2026-06-11

Fixed

  • /advance start FW outcomes — FWs recorded by the commish via /matchup fw (which sets status: complete) were previously invisible in the advance wizard. The response now includes a "FW outcomes recorded this week" section listing all completed FW games alongside the existing pending-FW checklist. Same fix applied to /preview.

v0.2.6

2026-06-09

Added

  • /golive command — members run this when they're about to stream their game. The bot posts a rich embed to a dedicated stream alert channel with the member's team name, stream link(s) (Twitch and/or YouTube — both shown if both are configured), current week label, and matchup details (opponent team name + mention for UvU games, "Simulation game" for CPU matchups). Fails gracefully with actionable ephemeral messages if stream alerts aren't configured, the member has no stream link, or the alert channel has been deleted.
  • /dd streams channel [#channel] (commish) — sets an existing channel as the stream alert destination, or auto-creates #stream-alerts when no channel is provided. Enables stream alerts in one step.
  • /dd streams enable (commish) — re-enables stream alerts after they've been disabled. Errors clearly if no alert channel has been configured yet.
  • /dd streams disable (commish) — pauses stream alerts without clearing the channel configuration, so re-enabling is instant.

Schema changes (run db:push against Railway after deploy)

  • League.streamAlertChannelId (String?) — stores the Discord channel ID for go-live announcements
  • League.streamAlertsEnabled (Boolean, default false) — feature toggle; preserves channel config when disabled

Changed

  • UvU and CPU game thread opening messages — both thread types now include /golive in their commands list so members are reminded to announce their stream when they're ready to play.

Note

This release adds a new command and a new subcommand group — npm run deploy must be run after deploy to register /golive and /dd streams with Discord.

v0.2.5

2026-06-08

Fixed

  • Thread names — matchup threads now use the member's Discord display name when no team is assigned, instead of the opaque User #### placeholder. The old fallback is retained if the user can no longer be fetched (e.g. they left the server).
  • FW DM notifications — commish DMs now show display names instead of raw <@id> mention syntax. Mobile push notification previews are now human-readable.
  • /advance start FW checklist — the FW pending section now shows exactly who to grant the win to (grant FW to @winner) and is labelled as a checklist rather than a generic warning.

v0.2.4

2026-06-04

Changed

  • OCR preprocessing — cropped regions are now 2× upscaled, converted to greyscale, histogram-normalised, and contrast-boosted before recognition. Improves accuracy across all stat screens without requiring higher-quality screenshots.
  • Tesseract page segmentation — switched from default PSM 3 (full page analysis) to PSM 7 (single text line). More appropriate for stat cell crops; reduces misclassification and slightly lowers CPU cost.
  • Numeric field cleanup — all stat columns (everything except player and team name) now go through a substitution pass (O→0, l/I→1, S→5, B→8, G→6, Z→2) followed by non-numeric character stripping. Fixes common OCR misreads that passed the text whitelist.
  • /stats submit low-confidence detail — the confirmation embed now shows the specific player and flagged field values for every low-confidence row (e.g. ↳ ⚠️ J. SMITH — comp: "l2", yards: "I87"), so users can judge whether to confirm or discard before reacting.
  • /stats submit confirmation messaging — footer and discard message are now contextual: when warnings are present, the footer advises confirming if values look right; the discard message no longer suggests re-uploading the same screenshots will produce a different result.

Fixed

  • Team name leading-digit strip — the L2 button prompt that appears near the team name region was already stripped in defense and blocking templates but not in passing, rushing, or receiving. Now applied consistently across all five stat templates.

v0.2.3

2026-06-03

Added

  • /roster retire @user (commish) — marks a member as retired when they leave the league. Retired members are immediately removed from the status board and no longer counted in the ready/not-ready tally. Historical matchup and stat data is preserved.
  • /roster unretire @user (commish) — restores a retired member to the active roster in case of a mistake or return.
  • /roster view — retired section — the roster view now shows a 🚫 Retired section below the active roster so commishes can see who has been retired.

Fixed

  • Stream link removal — passing clear, remove, -, or none as the twitch or youtube value in /roster add or /roster set now wipes the stored URL. Previously there was no way to remove a stream link once set.

Schema changes (run db:push against Railway after deploy)

  • Member.retired (Boolean, default false) — tracks whether a member has left the league

Note

/roster gained two new subcommands — npm run deploy must be run after deploy to register them with Discord.

v0.2.2

2026-06-03

Changed

  • /stats submit — week selection — new optional week integer option lets users submit stats for any week that has already started. Validates against existing current/complete weeks and replies with the valid range if the number is invalid. Defaults to the current week when omitted.
  • /stats submit — duplicate guard — before confirming, the bot checks whether stats already exist for that user + week. If a prior submission exists, the confirmation embed turns orange and the footer shows an overwrite warning. Reacting ✅ deletes all prior records in a transaction and writes fresh data; ❌ cancels with no changes.
  • UvU and CPU game thread opening messages — both thread types now include /stats submit in their commands list so players know how to log stats without leaving the thread.

Note

/stats submit gained a new option — npm run deploy must be run after deploy to register the updated command with Discord.

v0.2.1

2026-06-01

Added

  • /stats submit command — upload up to 7 post-game screenshots; the bot automatically parses all stat screens (box score, passing, rushing, receiving, blocking, defense) via Tesseract OCR, shows a confirmation embed with player/team counts and low-confidence warnings, and saves to the database on ✅ reaction.
  • OCR engine — fully calibrated for PS5 4K after-game screenshots. Classifier detects screen type (passing/rushing/receiving/blocking/defense1/defense2/boxScore) and layout (afterGame). Column coordinates tuned against real screenshots for all stat categories.
  • Defense two-screen merge — defense stats are split across two game screens; the bot accepts both screenshots and merges them by row position automatically.

Changed

  • Defense schema field — renamed defl to pdef to match the parsed column name (passes defended).
  • Classifier robustness — added column-header fallback detection for blocking and defense screens that don't expose a stat-type label in the standard region.
  • Defense merger — changed from hard throw on row count mismatch to a graceful trim with warning log; handles the noise rows in D2 that D1's threshold filter removes.

Schema changes (run db:push against Railway after deploy)

  • New models: GameStat, BoxScore, PassingStat, RushingStat, ReceivingStat, BlockingStat, DefenseStat
  • DefenseStat.pdef replaces the old defl field

Note

This is a new command — npm run deploy must be run after deploy to register /stats submit with Discord.

v0.1.11

2026-05-28

Fixed

  • /matchup set future-week bug — the week option previously accepted a raw integer that mapped to the internal database week number, not the display label. Entering 7 while in "Week 5" resolved to internal week 7 = display "Week 5" = the *current* week, so the matchup was treated as live, a thread was created immediately with the wrong week label, and the game appeared on the current board. All three week options (set, view, clear) now use the same autocomplete dropdown as /dd setup and /week set, presenting display labels ("Week 5", "Week 6"…) that resolve to the correct internal values.

Changed

  • /matchup view and /matchup clear error/response messages now show display week labels ("Week 7") instead of raw internal numbers ("Week 9").
  • /matchup set default description updated to clarify that omitting week defaults to *next* week (not current week).

Note

This release changes slash command option types — npm run deploy must be run after deploy to re-register commands with Discord and activate the autocomplete dropdowns.

v0.1.10

2026-05-28

Changed

  • Games section now shows User vs User matchups only — CPU/sim games are no longer listed on the status board. Anyone not in the games list is assumed to have a CPU game or bye week.
  • Section renamed from "Games This Week" to "User vs User Games This Week".
  • Thread links removed from board — game lines no longer include the → #thread channel link. Commissioners are added directly to threads (see below), making the link redundant.

Fixed

  • Reactions broken on split boards — leagues large enough to trigger the v0.1.9 multi-message split had completely non-functional ✅/🏝️ reactions. The reaction handlers were doing a direct string equality check against statusMessageId, which fails when it's stored as a JSON array. Both handlers now parse the array and check against the first message ID.

Added

  • Commissioners auto-added to UvU threads — when a User vs User matchup thread is created (via /matchup set or /advance confirm), all league commissioners are automatically added as thread members. This gives commishes visibility and admin access to every game thread without needing a link on the board.

v0.1.9

2026-05-28

Added

  • Manual advance mode/schedule cadence now accepts an advance option (auto or manual). In manual mode the week does not advance automatically when the deadline passes; instead the bot pings all commissioners in the league channel and waits for a commish to run /advance manually. Advance mode is shown in /schedule view.
  • /dd board — force re-post the status board for the current week (replaces the existing board without advancing the week). Useful when the board is lost or needs a fresh post.

Fixed

  • Status board overflow for large leagues — boards with 18+ members and many matchup lines previously crashed with DiscordAPIError[50035]: Must be 2000 or fewer in length. The board now splits into multiple messages when content exceeds Discord's 2000-character limit. Reactions remain on the first message; updates edit each chunk in place.
  • Multiple commissioners can now use all commish-gated commands (/dd board, /dd reset, /dd channel, etc.) — previously only the first commissioner ID was checked in some paths.
  • /dd subcommands are now visible to all registered commissioners, not just server admins.

Schema changes (run db:push against Railway)

  • League.autoAdvance (Boolean, default true) — stores manual vs auto advance preference
  • Week.manualAdvanceNotifiedAt (DateTime?) — tracks whether the commish ping has already been sent for a missed deadline in manual mode

v0.1.3

2026-05-25

Fixed

  • /matchup set no longer creates a thread immediately when the target week is not the active week — thread creation now only fires when the matchup's week number matches league.currentWeek; future-week matchups display "Thread will be created when that week is active" instead

v0.1.2

2026-05-25

Added

  • /dd commish add <user> — promote a user to commissioner
  • /dd commish remove <user> — revoke commissioner permissions (blocked if last commish)
  • /dd commish list — display all current league commissioners

Changed

  • /dd commish is now a subcommand group; the promote flow moves from /dd commish <user> to /dd commish add <user>

v0.1.1

2026-05-20

Added

  • /mydesk command — personal quick-ref for your team, stream links, and matchup
  • Sim/CPU matchup support via /matchup sim
  • Post-game ready reminders sent to UvU threads

v0.1.0

2026-05-18

Initial release

  • League setup, advance scheduling, and status board
  • Ready/away reaction tracking
  • UvU matchup threads with stream links
  • /schedule, /week, /roster, /matchup, /fw, /away, /games, /preview, /help
  • DST-correct timezone handling via IANA timezones
  • Railway + PostgreSQL deployment