All notable changes to this project are documented here. Format loosely
follows Keep a Changelog.
[Unreleased]
Changed
- Brand polish pass (in progress). The whole app is being brought onto the locked brand system - paper background, shell-green primary, brass and clay-orange accents, Barlow Condensed headings, Public Sans body, IBM Plex Mono tabular numerals in grids and tables, 8/12px radii, hairline borders instead of drop-shadowed boxes, light-only. Done so far: token system + fonts wiring; landing page rebuild; the public
/scores surface (event list, shooter averages, shooter profile with its trend/station/round-history views, readonly scoring grids); /login and /changelog; and the full scorekeeper dashboard - roster and event management, the live singles/doubles scoring grids and the button-mode sequential entry, round complete/void/delete controls, the sync-status indicator, and the historical CSV import wizard. Scoring marks are unchanged in shape - brand only adds colour (brass = hit, faint primary = miss), and the live scoring grids use the same soft brass wash for a scored hit as the read-only /scores grids rather than a solid fill. The printed ATA score sheet is deliberately left as a black-on-white facsimile; only the screen chrome around it is branded. - Identity foundation (slices a3-1 … a3-6). The move to one account per person with approval-gated club roles, laid in one migration sequence. a3-1 … a3-5 are applied and live; a3-6 is written and in the repo but not yet applied to any database — its enforcement is deliberately held until it has been validated on a branch database. The individual slices:
- Identity foundation, slice a3-1:
profiles (the scorekeeper-only person table, carrying club_id) is retired. accounts is now the single person record and club membership is sourced from club_roles. A signed-in account with no club role gets a "No club assigned" state on /dashboard instead of being bounced to login. The Grid/Buttons scoring preference moved from profiles to accounts; no scorekeeper action changes. Requires migration 20260909000000_person_table_merge.sql. - Identity foundation, slice a3-2 (backend only, no visible change): the club role catalog gains
club_deputy and renames scorekeeper to official_scorekeeper; each club now has exactly one primary (club_manager), enforced by a unique index, with a deputy cap of 3. Adds an accounts.is_platform_admin flag and helpers (is_club_manager_or_deputy(), is_platform_admin()) for later slices. Role management is still dashboard/DB-seeded — no UI yet. Requires migration 20260909000001_club_roles_v2.sql. - Identity foundation, slice a3-3 — minor protection. Sign-up now asks for a date of birth (required) and runs a server-side age check: under 13 can't create their own account (a parent must); 13–17 create a managed account (a parent can claim the managing link, and until then the club approves follow requests); 18+ is unchanged. Sign-up moved from a browser-only call to a server action in the process. Adds
accounts.date_of_birth / is_managed_override, shooters.date_of_birth, the is_managed() / age_years() / ata_age_class() functions, and a viewer_links.is_controller flag with its supervision RLS rules. Requires migration 20260909000002_minor_protection.sql. - Identity foundation, slice a3-4 (schema only, no visible change):
rounds gains the columns that later separate official from practice scoring and track official-round verification — is_practice, team_id, entered_by, verification_status (draft/pending_verification/locked/amended), verified_by_shooter_id, verified_at, self_attested. Existing completed rounds backfill to locked, everything else to draft. Nothing in the app reads or sets these yet. Requires migration 20260909000003_round_context_and_verification.sql. - Identity foundation, slice a3-5 — account-level averages. A shooter profile page (
/scores/shooters/…) for someone matched to their account now combines every club they shoot at into one set of career averages, trends, and station breakdowns, instead of showing each club's roster entry separately; the header notes "combined across N clubs" when it's more than one. Unmatched roster entries are unchanged. Practice rounds (once they exist) are excluded from these public averages. Adds the account_discipline_totals view. Requires migration 20260909000004_account_level_stats.sql. - Identity foundation, slice a3-6 — the RLS rewrite and public-site gate. Written and merged, but not applied to any database yet, so it has no effect on the live site. When it lands it will make every scores/roster/event read require a signed-in account (matching the
/scores redirect already in place), keep official results visible to any signed-in user while practice rounds stay private to the shooter, and scope official-round entry to club managers/deputies plus per-event assigned scorekeepers. Adds the event_scorekeepers assignment table and a dormant has_active_plan() hook for the future billing tiers. Ships with a per-rule spec (docs/a3-6-rls-spec.md) and a pgTAP matrix (supabase/tests/a3-6_rls_matrix.test.sql); promotion is gated on a branch-database validation run. Migration 20260909000005_rls_rewrite.sql. - Identity foundation —
/scores sign-in gate (the app half of a3-6, live now). The scores area (/scores — event results, round grids, shooter profiles) requires a signed-in account; a logged-out visitor is sent to the sign-in page. This lands ahead of a3-6's database-level enforcement so the redirect is in place before the RLS flip. The landing, pricing, sign-in, sign-up, and changelog pages stay open. src/app/scores/layout.tsx.
[0.2.0] - 2026-08-31
Field-hardening pass on the live scoring loop, plus a print-sheet
rebuild. Everything below was bench-verified; the scoring changes still
want live reps at a shoot.
Added
- Fast corrections on the scoring grid. Press and hold a cell (~450ms) to clear it back to unscored - a tap still cycles dead/lost instantly, and dragging cancels the hold so the grid still scrolls. A new "Undo last" button on the grid clears the cell you most recently tapped. Together these cover every stuck-mark case a tap cycle couldn't: a stray tap on an unshot cell, an entry on the wrong shooter's row, or a miskey from several taps back. Works in Grid and Buttons mode, Singles and Doubles (per bird).
- Offline-tolerant score entry. Every tap now goes into a durable local queue (localStorage) and renders instantly, then drains to the server one write at a time whenever there's a connection - on the next tap, on reconnect, on tab re-focus, and every 15s while anything is pending. A dropped signal mid-round, a reload, or backgrounding the browser no longer loses scores; entries that were showing before the reload are still there after it. A status pill above the grid shows "All scores saved" / "N not synced yet" / "Syncing N…" / "Offline — N waiting to sync". The two Complete-round buttons are disabled while anything is unsynced, so "remaining as LOST" can't write misses over targets still sitting in the queue. Applies to all four entry surfaces (Singles/Handicap grid, Doubles grid, and both sequential Buttons modes). Station subtotals and running totals are still computed server-side, so they lag offline entries and catch up on sync - the cell marks themselves are immediate.
Changed
- Doubles scoring reworked to follow the official White Flyer TRAP DOUBLES score sheet. Each pair is now scored as two marks, one per target - a slash for dead, a circle for lost - stacked one over the other, instead of a single glyph collapsing the pair (X / circle / diagonal). A numbered post column (1-5) was added down the left of every Doubles grid and printout. Station grouping and the running Total are unchanged. Applies to all four surfaces: the live scoring grid, the public read-only grid, the printable filled sheet, and the printable blank sheet.
- Printable score sheets (filled and blank, Singles/Handicap and Doubles) rebuilt to match the ATA landscape "TRAP SHOOTING SCORE SHEET" forms: blue title with the discipline under it, red field labels with underline rules, a "Squad Leader Initials" line, post numbers down both sides, and station grouping shown by a heavy rule after every 5th target instead of a Sub column - paper sheets don't carry per-station subtotals. Cells and rows are larger for legible hand-scoring. Doubles keeps the two-marks-per-pair layout (first bird over second) on both the filled and blank sheets. All four sheets now share one
ScoreGrid component and a white "paper" preview shell so they're reviewable on screen, not just in the print dialog. - Doubles live grid: each pair cell is now two independent tap targets (top = first bird, bottom = second), each cycling unscored -> dead -> lost. Replaces the single button that cycled the four pair outcomes (dead pair / dead loss / loss dead / loss pair). Scoring a pair is now two taps.
- Doubles Buttons (sequential) entry now shows just Dead and Loss, tapped once per bird - two taps per pair - matching how a station is called on the line. Replaces the four pair-outcome buttons. The prompt shows "Bird 1 of 2" / "Bird 2 of 2" and Undo steps back one bird at a time.
- Singles/Handicap scoring grids (live and public read-only) now draw a slash for dead and a circle for lost instead of the letters D / L, matching the marks the printed filled sheet already used and the new Doubles grids.
[0.1.0] - 2026-08-28
Versioning scheme changed as of this release. 1.0.0 is now
reserved for the first version live after the ATA formally adopts/
partners with this app - everything before that milestone is a 0.x.y
release, restarting the counter (the previous scheme had reached
1.3.0; that number is retired, not superseded by this one).
Added
- Sequential button entry as an alternative to tapping the grid: a per-scorekeeper account preference (Grid/Buttons toggle on the round page), showing one shooter/target at a time with big Dead/Loss buttons (or the four real Doubles pair outcomes) and a single-level Undo. Auto-advances through the squad in post order per target, then to the next target, matching how a station is actually called. Undo deletes the row rather than setting hit: false, since "undo" means "not yet scored," not "asserting a miss." Needs the
20260828000000_scoring_input_mode.sql migration applied before the preference can be saved.
Changed
- Buttons mode shows the full scoring grid above the sequential entry buttons rather than replacing it - the grid stays visible for reference/overview (station subtotals, who's been scored) while entering with buttons.
- Singles/Handicap scoring grid now uses "Dead"/"Loss" (D/L) terminology instead of "Hit"/"Miss" (H/M), matching real trap scoring language and the call-out labels Doubles already used.
- Scoring grid tap targets enlarged (32px -> 44px, the standard mobile touch-target minimum) on both the Singles/Handicap and Doubles live grids, for easier accurate tapping in the field.
Fixed
- Scoring grid tap delay: the box now flips the instant you tap it (optimistic UI) instead of waiting on the full server round-trip (save -> revalidate -> re-render) before showing any feedback. First live-field-test feedback item.
[1.3.0] - 2026-08-26
Added
- Real landing page at
/ — hero, feature highlights, and a scorekeeper/public split, replacing the one-line placeholder. Reflects the app's current, fully-public reality (no accounts or paywall) rather than any future state.
Changed
- App name corrected to TrueGauge in page metadata and on the landing page (previously "Trapshooting Scoring").
[1.2.0] - 2026-08-01
Fixed
- Blank score sheet row height now matches the completed-round printout (both
h-14) - blank sheets were previously taller, so the two didn't line up when compared side by side.
Added
- ATA classification (six-class system, Singles/Doubles) built against the actual rulebook brackets, but shelved behind a
CLASSIFICATION_ ENABLED flag showing "Classification — coming soon" instead of a computed letter: a class computed from this app's own recorded average isn't the shooter's real ATA classification, which is based on their official average aggregated across every club via ATA's own system. Flip the flag once an actual ATA data-sharing partnership exists - nothing else needs to change. - Score trend chart on shooter profile pages: one line chart per discipline (when a shooter has 2+ rounds in it), score per round in shot order rather than calendar-date spacing, with a hover tooltip showing the exact score and date. The existing round-history table serves as its accessible tabular view.
- Consistency stat on shooter profile pages: population standard deviation of a shooter's all-time scores per discipline, shown as "±X.X targets" once they have 3+ rounds in it. Plain figure, no invented "very consistent / variable" bands - those thresholds would just be a guess.
- Station breakdown on shooter profile pages: a 5-bar chart per discipline showing all-time hit rate at each station. Live rounds derive it from score_targets' target_number; historical rounds use their stored per-station subtotals directly, since there's no target-level detail to derive from.
- Edit an event's name/date, and permanently delete an event (cascades to its squads/rounds/scores) or a single round, each behind an explicit inline confirmation step. Deleting a round is distinct from the existing void: void keeps the record but excludes it from averages, delete removes it entirely and can't be undone.
- Search box on the events list (scorekeeper dashboard), filtering by name or date as the list grows - only shown once there are more than 5 events.
- In-app changelog page at
/changelog, linked from the version footer on every page, rendering this file directly rather than duplicating it - so it can't go stale relative to what's actually shipped. - Search box on the shooter roster (scorekeeper dashboard), same pattern and 5-shooter threshold as the events search.
- Career rounds-shot headline on shooter profile pages ("29 rounds shot all-time"), combining all three disciplines - previously that total was only visible split three ways across the per-discipline cards.
Changed
- Station breakdown bars now also show the average targets hit per round at that station (e.g. "4.7/5"), not just the percentage.
- Reworked the score trend chart around a linear best-fit trend line as the primary signal, with a clear "Improving / Declining / Steady" label and total-change figure above it - the raw per-round line alone was too noisy to read as an actual trend, so it's now de-emphasized supporting detail rather than the headline.
- Starting round 2+ on a squad now defaults the discipline selector to the previous round's discipline instead of always resetting to Singles.
[1.1.0] - 2026-08-01
Added
- Print-friendly completed round sheet (scorekeeper dashboard only, available once a round is complete or voided): shooter names, event/squad info, full hit/miss or pair-call-out grid, station subtotals, and final totals.
- Blank paper score sheet for hand-scoring as a connectivity-outage fallback, printable either ahead of a round (from the squad card, using its discipline selector) or from an in-progress round (pre- filled with the assigned shooters).
- CSV export of a club's full round history (scorekeeper dashboard, scoped to the logged-in scorekeeper's club): one row per shooter per round, with event, date, trap, discipline, status, handicap yardage, per-station subtotals, and final score.
- Historical round import (scorekeeper dashboard): upload a CSV of prior scoring data (Date/Shooter/RoundType/Station1-5/TotalScore, plus optional Location/Notes/Time), map each raw shooter name in the file to a roster entry or confirm it as a new shooter, review a full preview with every invalid row flagged (bad discipline, out-of-range station counts, unparseable dates) before anything is written, and resolve any TotalScore/station-subtotal mismatches per row or in bulk. Nothing is imported until the preview is explicitly confirmed.
- Historical rounds (imported without target-by-target detail) are now a first-class case throughout the app: the round page, public round page, print view, and shooter profile round history all show a plain station-subtotal summary instead of a fabricated grid, and they count toward career averages using their real recorded subtotals.
shooter_discipline_totals now unions live and historical rounds to make this work.
Changed
- Shooter profile averages now display as a per-round total (e.g. "22.58 / 25"), matching ATA's own convention, instead of a percentage - raw broken/shot counts are still shown alongside.
[1.0.0] - 2026-07-31
Initial public release.
Added
- Scorekeeper accounts with login-gated dashboard access.
- Shooter roster management: add/edit, active/inactive toggle, handicap yardage.
- Event and squad setup, with the squad-builder UI doubling as the walk-up squad-formation flow.
- Live round scoring for Singles, Handicap, and Doubles:
- Singles/Handicap: 25-target entry grid with per-station (/5) subtotals and running totals.
- Doubles: dedicated 25-pair (50-target) grid using dead pair / dead loss / loss dead / loss pair call-outs, with per-station /10 subtotals.
- Unrestricted post-completion editing, with an edited-after-completion indicator.
- Discipline selector on round start, with per-shooter handicap yardage snapshotted at round start (so a later roster yardage edit never changes a past round's recorded yardage).
- Round completion split into plain "Complete round" (requires full scoring) and "Complete, remaining as LOST" (auto-fills unscored targets as misses).
- Void round / revert-from-voided action, with unrestricted status transitions.
- Public, no-login scores view: event list, squad/round drill-down, and read-only scoring grids mirroring the scorekeeper view.
- Per-shooter public profile pages: career averages tracked separately for Singles, Handicap, and Doubles (matching ATA's Average Card structure), each with raw broken/shot counts, personal best, perfect-round counts, and full round history.
- Version footer shown on every page.
Fixed
- Yardage number inputs snapping to their minimum value on interaction; switched to text inputs with digit filtering.
- The scoring upsert's conflict clause not matching the widened 4-column unique constraint added for Doubles support, which silently dropped every score write with no visible error.
- Doubles score grouping colliding barrel 1 and barrel 2 rows under the same target number, corrupting the on-screen grid despite correct underlying data.