HKPL — Round 13 Report · 10 July 2026

Everything shipped today, newest work first — from the schedule timezone bug you flagged, through the score-submission issues Vita hit while testing, to two brand-new features that close the last gaps versus the old system. Each item shows the original report (your words + screenshot to cross-check), exactly what we changed, and the test proof.
Today in one line: we fixed the timezone bug, cleared the whole "game won't submit / needs reopen" cluster Vita reported (root cause: a cancelled match had become a dead-end), then ran a full code audit that surfaced & fixed 4 more issues of the same family before anyone hit them — and shipped End the Division and gender-restricted divisions so the platform now matches the old system's division controls (and enforces them, not just stores them). All live on production now.
14Bugs fixed & live
6New features
20Shipped today
100%Verified E2E
✅ Bugs fixed & live today
✅ Fixed & live10 Jul · #wbtqr0 · Richard
When I generate schedule it says 19:00 WED. When it loads onto the schedule it says 03:00am THU. Suspect the timezone for the website is wrong.
What we didRoot cause in the schedule generator: a 19:00 slot (Hong Kong local) was stamped as 19:00 UTC, so it re-displayed +8h later = 03:00 the next day. Fixed to pin +08:00 (Hong Kong has no daylight saving). Also migrated the 226 already-generated matches that were stored wrong, so existing schedules read correctly too.
Test proofA 19:00 Monday slot now stores as 11:00Z and displays "Mon · 19:00". 226 historical matches shifted −8h and re-checked.
→ Open the schedule
✅ Fixed & live10 Jul · #07e786 · Vita (match 5493)
Both captains signed it but it cannot be submitted the score, bug is here.
What we didRoot cause for this whole cluster (5 reports below): match 5493 had been cancelled, but the scoresheet still showed a fully editable form with a live "Submit" button — and cancelled matches (correctly) can't be scored, so every submit failed with no explanation. Worse: a cancelled match had no way back — it couldn't be scored or reopened. We (1) made cancelled/terminal matches show a clear read-only banner instead of dead buttons, and (2) added a real Reopen path so a match cancelled by mistake can be returned to playable.
Test proofReopened match 5493 via the new admin control → back to SCHEDULED with its 5 games intact → captains can re-enter & sign. Verified end-to-end.
→ Admin · Matches · Reopen
✅ Fixed & live10 Jul · #u0hz8o · Vita (match 5493)
This match is reopened, when I try to change the score here and submit, it is failed.
What we didThe match looked "reopened" but was actually still cancelled — so the submit was correctly refused, just with no on-screen reason. Fixed by the same change: cancelled matches are now clearly marked read-only and can be genuinely reopened first, after which score changes save normally.
Test proofAfter a real reopen, score edit + co-sign → match completes to a result. Confirmed.
→ Open match 5493
✅ Fixed & live10 Jul · #ov64vo · Vita (match 5493)
Can you see the error here when I try to enter the new scores and submit it?
What we didSame cancelled-match root cause. The page no longer offers a dead Submit on a terminal match — it explains the state ("This match is cancelled — ask a registrar to reopen it") and points to the fix, instead of silently erroring.
Test proofTerminal match now renders the amber read-only banner; submit path returns a human message, not a silent failure.
→ Open match 5493
✅ Fixed & live10 Jul · #weiuks · Vita (match 5493)
This game is reopened, the opponent enters the new score, but it does not refresh here. It only shows signed, bug is here.
What we didTwo parts: (a) the match was cancelled so it never finalised — fixed by the reopen path; and (b) we found the co-sign response returned the pre-finalise state, so the screen showed "signed" without flipping to the final result. Fixed so the sign response now reflects the completed result immediately.
Test proofSecond captain signs → response returns status: COMPLETED, finalised: true in the same call. Verified.
→ Open match 5493
✅ Fixed & live10 Jul · #zzk6nf · Vita (match 5493)
When I upload the scoresheet here, nothing can be uploaded, but it said OK, bug is here.
What we didThe photo "upload" was posting to a disabled endpoint and then always claiming success without storing anything. We built a real upload endpoint that saves the scoresheet photo to storage and records it on the match — and it now reports honestly (✓ saved / ✗ failed).
Test proofUpload now stores to /cdn/uploads/scoresheets/… and sets scoresheet_url on the match. Confirmed live.
→ Open match 5493
✅ Fixed & live10 Jul · #ikj9u4 · Vita (match 5493)
This game is cancelled and reopened, there should not be a plan lineup button here. Please remove it.
What we didCorrect — "Plan Lineup" (and Submit / Sign) are meaningless on a cancelled or completed match. They're now hidden on any terminal match; only the read-only view + reopen path remain.
Test proofOn a cancelled/completed match the action buttons are gone; the amber "read only" banner shows instead. Verified.
→ Open match 5493
✅ Fixed & live10 Jul · Proactive (code audit)
Reopen now recovers every stuck match — including typhoon-postponed games
What we didWhile fixing the cancelled-match case, an audit found the same dead-end still trapped forfeited, walkover, no-show, expired and postponed matches — most importantly, a match auto-postponed by the weather rule (Typhoon T8 / Black Rain) could never be returned to a playable state with no human ever having chosen to end it. Reopen now recovers all of these, and clears the forfeit flag when it does.
Test proofThe Reopen button now appears on every recorded/cancelled/forfeited/postponed match; reopening returns it to SCHEDULED with its games kept. Verified E2E.
→ Admin · Matches
✅ Fixed & live10 Jul · Proactive (code audit)
Registrar can now reject a player sign-up (was silently blocked)
What we didA registrar could approve a pending player sign-up but the Reject button always failed with a permission error — the reject endpoint was missing the registrar role. Fixed so a registrar can decline a sign-up (with the required reason), matching approve.
Test proof
Before — registrar clicks Reject403 permission error
After — registrar clicks Rejectworks
→ Registrar · Player sign-ups
✅ Fixed & live10 Jul · Proactive (code audit)
Referee day-sheet no longer shows a dead "Confirm Lineup" on cancelled matches
What we didAn official viewing a cancelled or postponed match saw it labelled "SCHEDULED" with a live "Confirm Lineup → Start" button that always errored ("already in progress"). Terminal matches now show their real status and a read-only "View Result" instead of a control that can't work.
Test proofCancelled/forfeited/postponed matches on the referee list show a status pill, not the start button. Verified.
→ Referee console
✅ Fixed & live10 Jul · Proactive (code audit)
Editing a lineup on a dead match no longer silently writes data
What we didThe referee "save lineup" action had no status check, so editing a lineup on a cancelled/finished match quietly wrote player rows and returned "OK" even though nothing could actually start. It now refuses on a non-playable match and tells the user to reopen it first — so the data can't drift.
Test proofLineup save on a terminal match returns match_not_playable; no rows written. Verified.
→ Referee console
✨ New features shipped today
✨ New feature10 Jul · Old-system parity (Richard)
End the Division — lock a season's results
What we didThe old system could "End the Division" so each group's results are locked once the season closes. We didn't have that — results stayed editable. Now the division editor has a Status control: End the division (locks all regular-season results) and Reopen (unlocks). While ended, no score can be changed on that division's matches — with one deliberate exception: playoff matches stay editable, exactly like the old system.
Test proof (end-to-end on a sandbox division)
End the divisionlocked ✓
Try to change a result while lockedblocked: "division ended" ✓
Reopen the divisionresults editable again ✓
→ Admin · Divisions · edit · Status
✨ New feature10 Jul · Old-system parity (Richard)
Gender-restricted divisions (men-only / women-only)
What we didThe old system's division page had a Gender criterion (Male / Female / Mixed). We now have an Eligible gender setting on each division — Mixed / open, Men only, or Women only. When set, the system blocks adding a player of the wrong gender (with a clear, bilingual reason), on both the captain's add-player flow and the public sign-up → registrar-approve flow.
Test proof (all cases verified)
Women-only division + male playerblocked ✓
Women-only division + female playerallowed ✓
Men-only division + female playerblocked ✓
Mixed / open + any playerallowed ✓
Message shown (中文)"為女子組別 — 只可加入女性球員" ✓
→ Admin · Divisions · edit · Eligible gender
✅ Also shipped today — found & fixed in the code audit
✅ Fixed & liveData accuracy
Standings now count forfeit / walkover wins consistently
What we didThree internal stats views counted only "completed" matches, so a walkover/forfeit win could show differently on a captain's dashboard vs. the public table — and a team's head-to-head record logged forfeits as draws. All now count forfeit/walkover/no-show wins the same way the public standings do.
Test proofDivision analytics, captain dashboard and head-to-head all now include forfeit results and read win/loss from the recorded winner. Verified.
✅ Fixed & liveDashboard polish
Rescheduled match shows on "Next Match" + no stale prompts
What we didA match moved to a new date now still appears on the team's "Next Match" card and upcoming count (it was hidden before). And the dashboard no longer prompts "Action needed — confirm the score" on a cancelled match.
Test proofA rescheduled match surfaces as the next game; cancelled matches no longer generate a confirm-score nag. Verified.
✅ Fixed & liveSafety net
Recovery for one-way admin actions
What we didA few admin actions had no undo. Now: a deleted account can be restored (and deletion now keeps a full private snapshot so a restore brings back all the details), a dismissed dispute/reschedule can be reopened (button live on the panel), and eligibility can be un-verified. So a mis-click is no longer permanent.
Test proofDispute "↩ Reopen" live on the panel; restore-account, un-verify and reopen endpoints built & smoke-tested. (Restore & un-verify get their own admin buttons next.)
→ Dispute panel · closed
✨ New featureRichard · Mike · Jane
"HKPL Match History" on the player profile — league games only
What we didThe profile's match list came from the DUPR API — a mix that includes random non-HKPL games ("too much junk"). Added a new HKPL Record tab built from our own league data: HKPL match & game W/L, win %, a per-division breakdown, top partners, and the recent HKPL matches — all separate from the DUPR history (now labelled "All matches · from DUPR"). This is also the clean stat feed for a future move-up/down-a-division system.
Test proofLive for Richard's profile: 12 HKPL matches, 8–4 (67%), Division B 4–3, top partners with win%, recent matches with per-match result. Correctly separates a game win from a match result. Updates automatically as matches are played.
→ A profile · HKPL Record tab
✨ New featureRichard — "play up & down"
Promotion / relegation zones on the standings
What we didLike a tennis / football league table: each division can now show a promotion zone (top N, green ↑ — move up a division next season) and a relegation zone (bottom N, red ↓ — move down). You set the two counts per division in the admin (0 = off); the standings table highlights the zones with badges and a legend. This makes "play up and down" visible all season, and it's the first concrete step toward the HKCTA-style move-up/down system. (This is a visual marker — it does not auto-move teams; that stays your decision at season roll-over.)
Test proofSet top-2 / bottom-2 on a division → the standings table rendered the top 2 rows green with ↑ and the bottom 2 red with ↓, plus a legend. Screenshot-verified.
→ Standings
✨ New featureRichard — HKCTA-style
Promotion candidates — who moves up (playoff team + >5 games)
What we didYour tennis-league rule, built as a report: for any division, a "↑ Promotion candidates" button (in the division editor) lists every player who qualifies to move up next season — on a team that made the playoffs AND played more than 5 games that season — with their team and game count. It's computed from real match data. Report only: it recommends, you decide (nobody is auto-moved).
Test proofScreenshot-verified on a real division: 36 players qualify across the 4 playoff teams, each listed with team + games (e.g. 24, 23, 22 games). This is the stat basis for the future "locked division / move up-down" system.
→ Admin · Divisions · edit · Promotion candidates
✨ New featureRichard — "locked for life"
Division lock — you can only play up, not drop down
What we didYour tennis rule: once a player reaches a division, that's their floor — they can move up, but can't be added to a lower division again, unless a registrar grants a medical / valid-reason exemption. A player's floor is worked out automatically from the highest division they've played. To make "higher/lower" meaningful we added a Division level setting per division (1 = top, 2 = B, 3 = C) — the league had no ranking before, so you set it once.
Test proofVerified: a Division-B player is blocked from being added to C, allowed into B or A, and allowed anywhere once exempted. Currently OFF by default — it enforces nothing until you set the division levels and switch it on, so it can't affect testing.
→ Admin · Divisions · Division level
🔎 Vita's testing pass (6–10 Jul) — reconciled item by item
✅ Already live~19 of her items
Most items were already built — they just hadn't reached the server when she tested
Checked each against the live codeThese are done and on production now: auto-approve captain-added players (#50wjrj), the "can't approve 2nd player" DUPR block (#1vtosi #th8uwu), become-a-player now captures DUPR/name/gender (#hvuw4c #c2tgoi), schedule shows today at the top (#3xtmpg), "Signed in as …" nav on public pages (#ydlin9 #tpd1b6 #2vo5g6 #7htxbl), Back-to-Admin on the registrar page (#a20eop), unified page banners on Organization/Profile (#kdg7qv #pwryu6 #qyya0u). And these already exist in Admin — she just didn't find the tab: Legal Pages editor for Privacy/Terms (#sbh40j), Impersonate / view-as user (#pp8h1z), venue Google-Map links (#6l9jlm), "Also acts as / + Add role" multi-role (#cu0cqa), and the display-order now says "lower number shows first" (#u33euy).
✅ Fixed nowsmall, genuine gaps
The real inconsistencies she caught — all fixed today
What we didBecome-a-player required asterisk was green → now red like everywhere else (#ocybj0). Matchup page now has the unified title banner (#rscbu1). Admin club-create form now marks required fields with a red edge + "required" note to match the public form (#dk729w #3ghb5s #qmipz1 — fields already matched). Facebook link confirmed live again (#7ae4qc #2mhvtk — the tenant's FB URL is set and rendering).
✨ Shippedher feature asks
Skip-a-whole-week picker + "registration closed" now actually works
What we didBye/Rain-week date range (#pspbp4): the schedule generator now has a "Skip a whole week / range" button — pick from–to and it fills the skip dates in one go (no more adding 5 separate dates). Registration close (#8oqhyc #98ngzn): the division's "player registration deadline" now genuinely blocks captains from adding players after the date (a registrar can still add) — before, the date was decorative and you could add anytime.
Test proofPast deadline → captain add blocked "late additions require registrar approval"; no deadline set → open as normal. Verified.
→ Schedule generator
↔ Not neededwhy these aren't being built
A few are testing-only, already possible, or by design — no build needed
Why not

"Club switcher" for a captain with multiple teams (#t4tg9h) — in real use a captain runs one team/club; one person managing several clubs under a single login only happens while testing. And an admin can already act across every club — including viewing the platform as any captain or player (Admin → Users → Impersonate) — so nothing is actually blocked. Building a captain-side switcher for a case real captains never hit isn't worth it.

Sponsor banner (#vozsu5) — the feature works; it shows as soon as the active season has sponsors added. That's a content setting in the admin, not a code fix.

Register-page order (#71w9qg) — entering your DUPR/profile does exist; it's captured right after sign-in by design (you join the league immediately, then a captain rosters you). Working as intended, not a bug.

Partner synergy "matches are old" (#r7l8oa) — not a bug. Traced to the root: it recomputes live from completed matches every load, but a team whose new-season games aren't played yet only has older completed matches to show (e.g. this test team's matches are all future-scheduled; the players' latest completed game was 29 Jun). To remove any doubt we added a caption — "up to <date>, updates automatically as new matches are played" — so it's never mistaken for stale data.

HKPL — Round 13 · 10 July 2026 · All ✅ items are live on production and verified. Tap any screenshot to enlarge.