What's New
Release notes and feature updates
/dogs Search Works Now
The search box on /dogs has been a dead input — type anything, nothing happens. Now it filters both the local-dogs grid and the SBHA-mirrored dogs section against name, registered name, and breed. Mirrors the behavior /handlers already had.
Working Search
- Search input wrapped in a real <form method='get'> with name='q' and a defaultValue from the URL
- Filters local dogs by name / registeredName / breed (case-insensitive substring)
- Filters SBHA dogs by registeredName / callName / breed against the same query
- Header counter switches from 'N dogs in the database' to 'N matches for "…"' when a query is active
- Empty-state stops nagging 'Load Demo Data' when the user just searched for something that didn't match
SBHA Dogs Directory on /dogs
Mirror of what landed for handlers in 2.49: every imported SBHA dog now shows up on the main /dogs page, not just the top 10 on the leaderboard. Listed below the local registered dogs, sorted by total mirrored points, with placement count + link straight to the SBHA dog profile.
Dogs Index
- New 'SBHA-Mirrored Dogs' section below the existing local-dogs grid on /dogs
- Sorted by total points across mirrored placements; shows breed, sex, and placement count
- Each row links to /sbha-dogs/[id] for the full placement history (with handler attribution)
- Discoverability fix: previously only top-10 SBHA dogs were reachable from the standings leaderboard
SBHA Placements Now Show 1st / 2nd / 3rd
Pure bug fix on the SBHA dog-page parser. Place was always parsed as bare numbers ("1", "2", "3") from the upstream table, but the parser only matched the formatted form ("1st", "2nd"), so every placement landed in the DB with place_label = NULL. Now they all carry the right label.
Parser Fix
- Switched dog.php result-row extraction from regex-across-all-cells to position-aware: locate the stake-link cell, then read cell+1 (place) and cell+2 (points)
- New formatPlace() helper suffixes bare numbers (1 → 1st, 2 → 2nd, 13 → 13th, 21 → 21st), passes through already-formatted values (RU-CH, JAM, etc.)
- Re-scrape: 71 of 71 results now have place_label populated (27× 1st, 25× 2nd, 19× 3rd)
- Visible on /sbha-dogs/[id] and /sbha-handlers/[id] — every placement row now leads with its place label
SBHA Dog Profiles + Cross-Linked Standings
Parallels the SBHA handler-page work: every imported dog now has a local profile page, and the Top Dogs leaderboards on /standings now stay on-site instead of opening southernbirdhunters.org in a new tab.
/sbha-dogs/[id]
- New read-only mirror profile for SBHA-scraped dogs (parallel to /sbha-handlers/[id])
- Shows breed / sex / color / whelped / FDSB# / sire / dam in a compact metadata grid
- Full placement list with stake/event, handler (linked to their /sbha-handlers/[id] page), season, and points
- Best-effort match to a local /dogs/[id] by registeredName surfaces a 'View local profile' button
Standings Cross-Linking
- SBHA Top Dogs leaderboard rows on /standings now link to /sbha-dogs/[id] for mirrored-only dogs (matched locals still link to /dogs/[id])
- SBHA per-division grids ditto — no more accidental new-tab navigation to southernbirdhunters.org
- Dog names on /sbha-handlers/[id] 'Dogs handled' list are now clickable links to /sbha-dogs/[id]
SBHA Handlers — Browse + Placement History
Two compounding wins on the SBHA mirror surface: every imported handler is now reachable from /handlers (not just the top 10), and each SBHA handler page shows the dogs they ran and the placements they earned.
Handlers Directory
- /handlers now includes a 'SBHA-Mirrored Handlers' section listing all 50 imported handlers, sorted by total points
- Search box filters both local users and SBHA handlers — typing 'tracy' surfaces George + Mike Tracy
Placement History on /sbha-handlers/[id]
- Migration 0019: sbha_results gains handler_name + sbha_handler_id columns (indexed)
- Stake-page scraper extended to walk the Dog/Handler row pairs in each placement table and write the handler back onto the matching result
- New 'Dogs handled by X' section on each SBHA handler profile — breed + sex + event + season + points per placement
- Re-scrape: 70 of 71 mirrored results now carry a handler reference
SBHA Handler Pages + Scraper Fixes
Three pile-on items after the first SBHA-handlers scrape went live: the scraper had a bug that pointed it at the wrong division IDs, a second bug that grabbed the site banner as the handler name, and the Top Handlers leaderboard links were going OUT to southernbirdhunters.org instead of staying on the local site.
Scraper Bug Fixes
- top_handler.php uses 3 handler-category division IDs (0/1/2 = Ladies/Walking/Horseback), NOT the 8 stake-type IDs the rest of the SBHA scrapers iterate. Replaced with a new TOP_HANDLER_DIVISIONS constant; all 8 fetches were previously returning empty
- parseHandlerDetail() was grabbing the first <h1> on the page, which is always the site banner ('Southern Bird Hunters Association'). Now skips a known set of template-noise <h1>s and picks the first real one
- Re-scraped: 50 handlers with correct names (Alan Atkins, George Tracy, Debbie Ozner…) and per-division points across Top Horseback / Top Ladies. Top Walking is empty at the source; will populate as SBHA publishes it
New /sbha-handlers/[id] Page
- Read-only local profile for SBHA-mirrored handlers who don't (yet) have a SportingDogs.app account
- Shows per-division points, season year, total, and the upstream southernbirdhunters.org link for provenance
- Best-effort match to local users by name — if the handler has a SportingDogs.app account, a 'View local profile' button appears
- Footer prompts unclaimed handlers to register
Standings Leaderboard Linking
- SBHA Top Handlers rows on /standings now link to the local /sbha-handlers/[id] page instead of opening a new tab on southernbirdhunters.org
Multi-Day Brace Distribution + Share Public Results
Two demo-deck items from the SBHA notes: brace draws now distribute across the event's date range instead of dumping everything onto day 1, and every event has a one-click 'Share results' button that copies the public results URL.
Brace Draw — Multi-Day Aware
- drawBracesForStake() now reads event.startDate / endDate and spreads braces evenly across the day range (clamped 1–14 days)
- Each brace gets a default scheduledTime computed from event.startTime + stake.durationMinutes + a 5-minute gap between braces
- Secretaries still override per-row day, time, and course in the existing brace edit UI — the auto-distribute is a starting point, not a constraint
- Existing braces are not re-distributed automatically — re-run the brace draw to apply the new layout
Share Public Results
- New 'Share results' button on every event detail page, alongside Entry List / Running Order / Results
- One-click copies the public results URL to the clipboard with a 'Copied!' confirmation
- Falls back to a prompt dialog if the Clipboard API is blocked (older browsers, insecure contexts)
Deploy Hardening (internal)
- New deploy/ship.sh — local-build → atomic-swap workflow with auto-rollback and old-release pruning
- Droplet post-receive hook neutered so 'git push production main' can no longer OOM the 1 GB box
- deploy/sportingdogs.service synced to match the installed unit; deploy/README.md rewritten around ship.sh
SBHA Handlers + Per-Division Standings + Event Cross-Linking
Closes out the remaining items from the morning batch: top_handler.php scraper feeding a new sbha_handlers + sbha_handler_results pair, SBHA panels on event detail and handler detail pages, and per-division leaderboards on /standings and the dog profile.
Handler Scraper
- New /lib/scrapers/sbha-handlers.ts walks top_handler.php?division=N for all 8 divisions and drills handler.php?contactID=N for each handler's name + location
- Migration 0018 adds sbha_handlers + sbha_handler_results tables (per-division points + rank, season-aware)
- POST /api/admin/scrape/sbha-handlers — third Run button on /admin/scrape with Max handlers cap
- Shared division classifier (lib/sbha-divisions.ts) maps stake_type IDs and free-text stake names to 8 normalized division labels
Cross-linking SBHA into Local Surfaces
- /events/[id] — new 'SBHA Record' panel matches local event name to mirrored stakes and shows judges + entry breakdown (pointers/setters/other)
- /handlers/[id] — new 'SBHA Standing' panel with per-division points + rank when handler name matches a mirrored sbha_handlers row
- /dogs/[id] — SBHA panel now groups placements by division with sub-totals, mirroring the SBHA standings structure
Standings Page
- New 'SBHA Top Dogs by Division' grid — top 10 per division across all 8 SBHA divisions, season-aware
- New 'SBHA Top Handlers' overall leaderboard — sums points across every division per handler
- Existing 'SBHA Top Dogs' overall leaderboard kept as a quick-scan summary above the per-division grid
SBHA Dog Registry Import + Bird-Dog Image Cleanup
Real implementation of the SBHA dogs/points/stakes scraper (Phase C4 follow-up), bird-dog category chips on /dogs, and a demo-mode override that finally retires the retriever-era Unsplash photos seeded into existing dogs and feed posts. Imported SBHA points show up on /standings as a new 'Top Dogs' leaderboard, and on each dog profile when we can match by registered name.
SBHA Dog/Points/Stakes Scraper (real impl)
- New /lib/scrapers/sbha-dogs.ts powered by node-html-parser — walks 8 points-by-stake-type pages, drills each dog.php?dogID, then each stake.php?stakeID for judges + entry breakdowns
- Three new mirror tables (sbha_dogs, sbha_results, sbha_stakes) — UPSERT by upstream sbha_id; not FK'd to local dogs/events so re-scraping is cheap
- POST /api/admin/scrape/sbha-dogs — super-admin route accepting { maxDogs, maxStakes } caps so one-off runs stay bounded
- Politeness baked in: 200ms inter-fetch delay, 8s timeout, single 5xx retry — friendly to the source site
Admin Scrape Runners
- New /admin/scrape page (super-admin only) with Run buttons for both the SBHA Clubs and SBHA Dogs scrapers
- Inline ScrapeStats display: pages fetched, rows upserted, expandable error log
- Card on /admin dashboard surfaces the page for super-admins only
SBHA Points on Standings & Dog Profile
- New 'SBHA Top Dogs' leaderboard at the bottom of /standings — top 25 by mirrored cumulative points, season-aware
- Each row deep-links to the local dog profile when we can match registeredName, otherwise to the upstream dog.php URL
- Per-dog 'SBHA Standing' panel on /dogs/[id] — placements with judges + entry counts when the stake is mirrored
Bird-Dog Breed Chips & Photo Cleanup
- /dogs filter chips now match the registration form categories under DEMO_BIRD_DOG_ONLY: Pointers/Setters · Pointing Breeds · European/Rare Breeds
- New isSeedPlaceholderUrl() helper detects seeded Unsplash / placedog / picsum URLs
- Demo mode overrides those at render time on dog cards, dog profile banners, and feed posts so the AKC breed fallback wins instead of a stale retriever photo
- User-uploaded dog_photos rows are still always preferred — only legacy seed photoUrl/mediaUrl gets swapped
Phase C Wrap: PhotoCredit + Scraper Scaffolding
Closes out the Phase C deliverables with a reusable photo-credit component (C1) and the scraper scaffolding for American Field calendar imports (C3) and SBHA club imports (C4). Original change-request list is now 100% addressed in code — A1-A5, B1-B8, C1-C5 all shipped or scaffolded.
PhotoCredit Component (C1)
- New <PhotoCredit /> component with three variants — overlay (banner strip), inline (text below image), corner (small pill)
- Always renders a hyperlinked credit so attribution is one click away from verification
- Refactored the home hero (Chris Mathan) and dog profile banner stock-photo strip to use it — single source of truth
- Future surfaces (handler avatars sourced from public photos, breed-detail pages) can drop in the same component
American Field Scraper Scaffold (C3)
- New /lib/scrapers/american-field.ts with importAmericanFieldCalendar() function and ScrapeStats type
- POST /api/admin/scrape/american-field — super-admin-only manual trigger
- Currently returns a 'not yet implemented' stats object so the operator gets a clear signal
- Stable function shape + admin route mean the actual scrape implementation can land in an ops cycle without UI/admin changes
- TODO list in the source file lays out the implementation path: fetch /calendar, parse rows, dedup by (clubName + startDate), parse linked PDFs for points-trial / qualifying / judges metadata, rate-limit + retry-after handling
SBHA Club Scraper Scaffold (C4)
- New /lib/scrapers/sbha-clubs.ts with importSbhaClubs() function (shares the ScrapeStats type)
- POST /api/admin/scrape/sbha-clubs — super-admin-only manual trigger
- TODO list in the source: fetch /regions.php, walk every clubID, parse per-club detail pages, match-or-insert against existing clubs by (name + state), ensure the SBHA association link in club_associations
Original Change Request: Status
- A1 (hide non-bird-dog) ✓ · A2 (home page + Stakes) ✓ · A3 (events filters) ✓ · A4 (clubs multi-assoc) ✓ · A5 (UserMenu reorder) ✓
- B1 (Handlers index) ✓ · B2 (event judges) ✓ · B3 (points-trial filter) ✓ · B4 (dogs default order) ✓ · B5 (multi-org reg) ✓ · B6 (multi-photo) ✓ · B7 (affiliations) ✓ · B8 (avatar edit) ✓
- C1 (Mathan photos) ✓ caption infrastructure (full library swap = licensing/content work) · C2 (breed fallback) ✓ · C3 (AF scraper) scaffold ✓ · C4 (SBHA scraper) scaffold ✓ · C5 (club geo + radius) ✓
Phase C: Club Geo + Breed Photo Fallback
Two of the four remaining Phase C deliverables shipped: full club geo with 'near-me' radius search (C5) and AKC breed-photo fallback for dogs without uploaded photos (C2). Phase C kicks off the data-and-imagery work the deck called out.
Club Geo + Radius Search (C5)
- New clubs.latitude / clubs.longitude decimal columns (migration 0016)
- Backfill: every existing club with a state code got seed coords from the 50-state population-weighted centroid table — 16 rows seeded on production
- /clubs page gets a 'Near' ZIP+radius form (preset radii: 25, 50, 100, 200, 500, 1000 mi)
- Haversine filter limits the result list and adds a 'NN mi away' chip per card
- Sort flips to 'nearest first' when geo is active, otherwise stays upcoming-events-desc → alpha
- Search bar + assoc + state chips all preserve the geo params across navigation
- ZIP centroid table extended with state-level helper (lookupState) for any future feature that needs a coarse fallback
Breed Photo Fallback (C2)
- New /lib/breed-photos.ts maps each FDSB-eligible bird-dog breed to an AKC public photo URL with attribution metadata
- Dog profile banner uses dog_photos[0] → legacy dog.photoUrl → AKC breed fallback in priority order; banner credit caption ('Stock photo: AKC') links to the breed's AKC page
- /dogs index card shows the same fallback when a dog has no uploaded photo, with a small 'Stock: AKC' chip in the corner
- When the licensed Chris Mathan library lands, swap out the URLs in breed-photos.ts and the credit fields — no other surfaces need touching
- next.config.ts remotePatterns + middleware CSP both updated to allow www.akc.org and image6.photobiz.com hosts
Phase C Roadmap
- C5 ✓ shipped — clubs lat/lng + 'near me' radius search
- C2 ✓ shipped — AKC breed-photo fallback with attribution
- C1 — Chris Mathan asset library: caption infrastructure ready; full library swap is licensing/content work outside the codebase
- C3 — American Field calendar scraper: scaffolding lands in 2.44.0, real scrape impl needs ops cycle
- C4 — SBHA club scraper: same shape as C3, scaffolding lands in 2.44.0
Nav Cleanup + Clubs State Filter (Phase C kickoff)
Two more nav-tightening fixes from feedback, plus the first Phase C deliverable: a multi-state filter on /clubs with per-state counts. Phase C remaining items (full club geo + radius, breed-photo fallback, scraping scaffolding) ship in 2.43.0–2.44.0.
Nav: Add Dog Moved to Avatar Menu
- 'Add Dog' button removed from the main nav bar — was eating ~120px on every layout
- New 'Register a Dog' primary CTA at the top of the avatar dropdown (highlighted with primary-color background)
- Mobile menu still has the inline Add Dog button at the bottom for thumb-reach
Nav: Shorter Search
- Search input shrunk from w-28/2xl:w-56 → w-24/2xl:w-40 — saves another ~30px at every breakpoint
- Placeholder still 'Search…'; focus ring + behavior unchanged
Clubs: Multi-State Filter (Phase C5 lite)
- New 'Filter by State' chip row above the existing 'Filter by Association' chip row on /clubs
- Each state chip shows the number of clubs in that state inline (e.g. 'GA (4)')
- Multi-select — pick any number; chips toggle on click, encoded as ?state=GA&state=AL&… in the URL (bookmarkable + shareable)
- Search bar preserves both assoc + state filters across submits via hidden inputs
- Assoc chip URLs preserve state codes (and vice versa) — no flicker losing filters when toggling either dimension
- 'Clear' chip when any states are selected
Phase C Roadmap
- C5 (full geo + 'near me' radius search on /clubs) — needs lat/lng schema; ships in 2.43.0
- C2 (breed-photo fallback when a dog has no uploaded photo) — uses public AKC images with photo credit; ships in 2.43.0
- C1 (Chris Mathan asset library) — caption infrastructure for now; full library swap is content/licensing work outside the codebase
- C3 (American Field calendar import) + C4 (SBHA club scraper) — scaffolding ships in 2.44.0; real scrape implementations are ops work that need testing against the live source sites
Litter Waitlist Signups
Closes the breeder workflow loop started with Litter Announcements (2.22.0). Prospective puppy buyers can join a litter's waitlist with an optional message; breeders see the running list of interested buyers (with contact info) on the litter's edit page. Notification fires when someone signs up.
Schema
- New litter_waitlist table (id, litter_id, user_id, message, created_at)
- FKs to litters and users with ON DELETE cascade
- UNIQUE INDEX on (litter_id, user_id) prevents duplicate signups
- Migration 0015 applied to production
API
- POST /api/litters/[id]/waitlist — auth required, rate-limited 30/min/IP, message capped at 4000 chars, breeder can't waitlist their own litter (400)
- DELETE /api/litters/[id]/waitlist — removes the current user's row only
- Returns 409 with friendly error when user is already on the list
- Inserting a row fires a system-kind notification to the breeder with a link to /litters/[id]/edit
UI
- <JoinWaitlistButton /> on litter detail pages — adapts to viewer state: guest sees a Sign-in link; signed-in non-breeder sees a 'Join waitlist' button that opens an inline message form; already-on-list shows a green 'On waitlist' chip with click-to-leave; breeder sees a read-only 'N on waitlist' counter
- Optimistic-flip UI for join/leave with revert on failure
- Breeder waitlist viewer on /litters/[id]/edit — name, location, email, message, join date for every signup; click name to jump to /handlers/[id]
- Empty-state copy when no one's joined yet
Nav Restructure: Dropdown Groups + Login Restored
Two fixes called out in feedback. (1) Login affordance was lost — the chevron on the avatar button was gated to 2xl so it looked like decoration; the Sign In link only appeared at sm+. (2) Nav was overcrowded at 7 top-level items. Restructured into 3 direct links + 3 grouped dropdowns (Dogs, Trials, Help) so it scans cleanly at every breakpoint.
Login Affordance Restored
- Avatar button: chevron is now ALWAYS visible (no longer gated to 2xl) — clearly indicates the dropdown
- Avatar button: chevron rotates 180° when the menu is open
- Avatar button: padding widened (gap-1.5, pr-2) so chevron has visual room
- Signed-out: 'Sign In' is now visible at every breakpoint — icon-only below sm, full label at sm+
- Signed-out: 'Sign up free' button stays at sm+ as the secondary CTA
Grouped Dropdowns
- Top-level shrinks from 7 flat items to 3 direct links + 3 dropdowns: Events · Dogs▾ · Trials▾ · Feed · Help▾
- Dogs▾: Dogs (browse), Handlers, Litters, Compare Dogs
- Trials▾: Clubs, Standings
- Help▾: Documentation, What's New, Platform Stats
- Each dropdown row shows the icon, label, and a one-line description
- Reusable NavDropdown component — consistent styling, outside-click close, chevron rotation, active-state highlighting when any child is the current page
Mobile Menu
- Hamburger mirrors the desktop grouping: direct links first, then Dogs / Trials / Help sections each with their own header
- Cleaner vertical flow on small phones
Printable Event Results — Secretary Trilogy Complete
Third leg of the secretary print trilogy. Joins the Entry List (2.38.0) and Running Order (2.25.0) prints — every event now has bookmarkable, public, letter-paper-optimized PDFs for the full pre-trial → mid-trial → post-trial workflow.
Results Print Page
- New /events/[id]/results/print — public, server-rendered, letter paper
- Banner: 'Official Results' label, event name, dates, grounds, city/state
- Summary: stake count + total placements recorded
- Per stake section sorted by run order with participant type + category
- Per row: color-coded placement pill (1st amber/gold, 2nd silver, 3rd bronze, 4th, JAM, RJ, Pass, Fail, Scratched, Withdrew) · Dog (registered + breed) · Handler (name + location) · Score · Notes
- Placements within each stake sorted by canonical order (1st → 2nd → 3rd → 4th → JAM → RJ → Pass → Fail → Scratched → Withdrew)
- Page-break-inside: avoid on each stake
Surfacing
- New 'Print' button at the top of /events/[id]/results
- Direct URL: /events/[id]/results/print — bookmarkable + shareable
- Open Graph metadata for nice social previews
Print Trilogy
- Pre-trial: /events/[id]/entries/print — who's entered (2.38.0)
- Mid-trial: /events/[id]/braces/print — running order with brace times + courses (2.25.0)
- Post-trial: /events/[id]/results/print — placements with scores + notes (this release)
- All three follow the same letter-paper / @page / page-break-inside: avoid pattern; nav, header, footer, and Print buttons hidden via @media print
Printable Entry List
Companion to the running order print (2.25.0). Public, letter-paper-optimized list of every dog entered in the event grouped by stake — used by club secretaries before the brace draw runs and by competitors checking who they're up against.
What's on the page
- Banner: 'Official Entry List' label, event name, date range, grounds, city/state
- Summary: stake count + total entries, plus 'entries close' deadline when set
- One section per stake (sorted by run order, with participant type + category)
- Per-entry row: # · Dog (registered name + call name) · Breed · Sex · Handler (name + location) · Registration (every org/number on file via the multi-org table from 2.34.0)
- Withdrawn entries excluded automatically
- Page-break-inside: avoid on each stake so it doesn't split mid-table
- Generated timestamp + SportingDogs.app branding in the footer
Where to find it
- New 'Entry List' button at the bottom of /events/[id], next to 'Running Order' and 'Results'
- Direct URL: /events/[id]/entries/print — bookmarkable, shareable, public
- Open Graph metadata for nice social previews when shared
Hard Fix: Horizontal Scroll Eliminated
Earlier nav-overflow attempts (2.30.0, 2.31.0) shrank the right cluster but still allowed page-level horizontal scroll on certain widths. This release adds a definitive overflow-x-clip safety net at the html + body level AND pushes all the nav-label gates from xl (1280px) up to 2xl (1536px) so labels only appear when there's actually room inside the centered max-w-7xl container.
Page-Level Safety Net
- <html> and <body> get overflow-x-clip — no element can ever cause a horizontal scrollbar on the whole page, regardless of which sub-component goes too wide
- Vertical scroll is unaffected (overflow-x-clip is x-axis only)
Nav Label Breakpoints
- All top-level nav labels (Events, Clubs, Standings, Dogs, Handlers, Litters, Feed, Stats) — gated to 2xl
- Help dropdown label + chevron — gated to 2xl
- Add Dog button label — gated to 2xl (still shows the icon)
- Avatar button name + chevron — gated to 2xl
- Search input — w-28 below 2xl, w-56 at 2xl+
- At md/lg/xl: icon-only nav with tooltips and a tight right cluster fits comfortably inside the max-w-7xl container
- At 2xl (≥1536px): full labels return because there's actually horizontal room
Handler Affiliations
Phase B7 of the SBHA demo change-request batch. Handlers can list the clubs, associations, and other organizations they belong to — pick from the dropdowns of known clubs and associations on the platform, or type a free-text name. Each affiliation can include a role and a 'since' year. Renders publicly on /handlers/[id].
Schema
- New user_affiliations table (id, user_id, club_id?, association_id?, free_text_name?, role?, since_year?, sort_order, created_at)
- Either club_id, association_id, or free_text_name must be set — server enforces
- FKs use ON DELETE SET NULL so deleting a club/association doesn't wipe the affiliation row, just the link
- Indexed on user_id, club_id, association_id
- Migration 0014 applied to production
API
- POST /api/me/affiliations — owner-only, validates FK ids, year range 1900–next year, role ≤ 64 chars, free-text ≤ 128 chars
- DELETE /api/me/affiliations/[id] — owner-only, scoped to (user, affiliation)
- Rate-limited 30/min/IP
UI
- New 'Affiliations' section on /profile with the AffiliationsEditor component
- Type selector (Club / Association / Other), context-appropriate input (club dropdown / assoc dropdown / free-text input), optional role + since-year
- List of existing rows with trash-icon delete; empty-state copy when none
- Public 'Affiliations' section on /handlers/[id] — renders for everyone when the handler has any rows; club entries link through to /clubs/[id]
Up to 4 Photos Per Dog
Phase B6 of the SBHA demo change-request batch. Dogs can now have up to 4 photos — banner stays the primary, the rest render in a 'More Photos' grid below the stats. Existing single-photo dogs were backfilled into the new table on deploy.
Schema + Backfill
- New dog_photos table (id, dog_id, url, caption, sort_order, created_at) with a dog_id index
- Migration 0013 includes a one-shot backfill — every dog with a non-empty dogs.photo_url got a row in dog_photos at sort_order 0. 12 rows backfilled on production.
- Legacy dogs.photo_url column kept for backwards compat — banner fallback when dog_photos has no rows for a dog
API
- POST /api/dogs/[id]/photos — owner-only. Body { url, caption?, sortOrder? }. Returns 409 when the dog is already at the 4-photo cap.
- DELETE /api/dogs/[id]/photos/[photoId] — owner-only, scoped to (dog, photo)
UI
- /dogs/[id]/edit now renders a full DogPhotosEditor instead of a single FileUpload — grid of existing photos with delete buttons + an upload slot when there's room
- First photo in the grid is labeled 'BANNER' so owners know which one renders on the public profile
- Dog profile banner pulls from dog_photos[0] (with fallback to legacy photoUrl)
- New 'More Photos' section on the dog profile renders below the stat cards when there are 2+ photos
- Brag sheet banner uses the same first-photo logic for consistency
Multi-Org Dog Registrations
Phase B5 of the SBHA demo change-request batch. Bird dogs typically carry pedigrees with multiple registries (FDSB + AKC + UKC + NAVHDA + ABC, etc.) — now the app captures every one of them. Existing single-org registrations were backfilled into the new table on deploy.
Schema + Backfill
- New dog_registrations table (id, dog_id, organization, registration_number, sort_order, created_at) with a dog_id index
- Migration 0012 includes a one-shot INSERT … SELECT that pulled every existing dog.registrationOrg + dog.registrationNum pair into the new table — 10 rows backfilled on production
- Legacy dogs.registrationOrg / dogs.registrationNum columns kept for backwards compat — UI still writes the primary org there at create time
API
- POST /api/dogs/[id]/registrations — owner-only, allow-listed orgs (AKC, UKC, CKC, FDSB, NAVHDA, ABC, PEDIGREE, Other), rate-limited 30/min/IP
- DELETE /api/dogs/[id]/registrations/[regId] — owner-only, scoped to (dog, registration)
UI
- /dogs/[id]/edit now has an 'Additional registrations' section under the primary org/number pair
- DogRegistrationsEditor: list of existing rows with trash-icon delete, plus an inline add form (org dropdown + number input + Add button)
- Dog profile owner-line renders the full list inline (e.g. 'Owner: Angela · FDSB #SS12345 · AKC #SR987 · UKC #UR55')
- Backwards compatible: dogs with only the legacy columns still display correctly via a fallback
Points Trial / Qualifying Filters + Profile Editing
Phases B3 and B8 of the SBHA demo change-request batch. Events can now be flagged as 'Points Trial' or 'Qualifying Event' with public yes/no filters and pills on event cards; handlers can edit their profile (name, location, bio, avatar) inline on /profile.
Points Trial / Qualifying Event Filters (Phase B3)
- Two new boolean columns on events: points_trial and qualifying_event (migration 0011)
- EventFilters: tri-state chip rows (Any / Yes / No) for each, in the expanded filter panel
- URL params: ?points_trial=yes|no, ?qualifying=yes|no — bookmarkable + shareable
- Admin event editor checkbox row gains 'Points Trial' and 'Qualifying Event' toggles
- Public event cards: amber 'Points Trial' pill + emerald 'Qualifier' pill render automatically when set
- POST /api/admin/events and PUT /api/admin/events/[id] both accept the new flags
Profile Editing (Phase B8)
- New PUT /api/me endpoint — owner-only, accepts { name, location, bio, avatarUrl }, validates lengths and rate-limits 30/min/IP
- /profile page banner now toggles between read-mode and edit-mode with a Pencil button
- Edit mode: FileUpload-backed avatar (3MB max, JPG/PNG/WebP), name, location, bio (4000 chars)
- Cancel restores all fields; Save flips back to read mode with the new values
- New 'user-avatar' upload kind added to the FileUpload component + /api/uploads
Event Judges + Dogs Default Ordering
Phase B2 + B4 of the SBHA demo change-request batch. Club secretaries can now publish a list of judges per event (with optional stake assignment), and the /dogs index defaults to showing your own dogs first.
Event Judges (Phase B2)
- New event_judges table with eventId, optional stakeId (NULL = whole trial), name, optional role, optional notes, sort order
- POST /api/admin/events/[id]/judges — admin/secretary only, validates stakeId belongs to the event
- DELETE /api/admin/events/[id]/judges/[judgeId] — admin/secretary only
- Public Judges section on /events/[id] — renders above Stakes when any judges are listed; shows name, role, stake assignment, notes
- New JudgesEditor in /admin/events/[id] — name + role + stake dropdown + notes, plus existing-judges list with remove buttons
- Migration 0010 applied to production
Dogs Index Default Order (Phase B4)
- When signed in, your own dogs are listed first, then everyone else's by recency
- Each of your dogs gets a primary-color border ring + a small 'YOU' pill in the corner of the photo
- Header subtitle calls out the count: 'N dogs in the database · your X dogs listed first'
- Signed-out viewers see the previous recency-only ordering
Handlers Directory + Real Nav Overflow Fix
Phase B1 of the SBHA demo change-request batch — new public Owners/Handlers index — plus a proper fix for the nav-overflow regression spotted in the latest screenshot. Avatar button no longer pushes 'Angela' off the right edge of the page.
Real Nav Overflow Fix
- Avatar button: name + chevron now hide below xl (≥1280px) — at md/lg the avatar circle alone keeps the right cluster from overflowing the nav. Name + email still appear in the dropdown content when opened.
- All nav-link labels gated to xl instead of lg — Events, Clubs, Standings, Dogs, Handlers, Litters, Feed all collapse to icon-only at md/lg with tooltip; full labels at xl
- Help dropdown: 'Help' label + chevron also gated to xl
- Add Dog button: 'Add Dog' label gated to xl, plus icon shown alone below
- Search input: w-28 below xl, w-56 at xl+
- Net effect: the entire nav fits comfortably from md (768px) up through xl, with the rich-label experience at typical desktop widths (≥1280px)
Owners/Handlers Directory (Phase B1)
- New /handlers index page — public, sortable, searchable
- Cards show avatar (or initials), name, location, dog count, title count, competition count
- Search by name or location; sort by Most dogs / Most titles / A→Z (URL-encoded so links are bookmarkable)
- Hides users with zero dogs by default (still findable via search)
- Each card links to the existing /handlers/[id] résumé page
- New top-level 'Handlers' tab in the nav (Users icon, between Dogs and Litters)
- /handlers added to /sitemap.xml
- Open Graph + Twitter card metadata for shareable previews
Demo Polish: Nav, Filters, Hero, Avatar Menu
Phases A3+A4+A5 of the SBHA demo change-request batch, plus two visual fixes called out from the screenshots. Multi-select Association filters on /events and /clubs, future-events default with a 'Show archived' toggle, Youth moved from Event Type to Participant Type, avatar menu reordered per deck, and a new /me/dogs page.
Navigation Fixes (in response to screenshots)
- Add Dog button: whitespace-nowrap so 'Dog' can no longer break onto a second line; collapses to icon-only at md breakpoint
- Search input: tightened to w-32 at md, w-56 at lg
- Top-level nav: less-frequent items (Clubs, Standings, Litters, Feed, Stats) collapse to icon-only at md, full labels return at lg
- Bird-dog demo mode: 'Stats' removed from top-level nav (most stats are retriever-flavored and already hidden) — moved under the Help dropdown so the URL still works
- Hero photo: switched from object-right to object-center because the Chris Mathan photo has the dog on the LEFT of the source image — anchoring right was clipping the head off
Events Page (Phase A3)
- Association filter is now multi-select (chip toggles) — pick any number of SBHA, NBHA, AFTCA, ABHA, USCSDA, NGSPA, NRSFTC, NVA
- Default to future events: when no explicit start date is set and 'Show archived' is off, today is used as the floor
- New 'Show archived (past) events' toggle next to 'Only show events I've saved'
- 'Youth' moved from Event Type chips to Participant Type chips, per the updated SBHA deck (page 3)
- Explanatory copy rewritten: 'Calendar of upcoming American Field-recognized field trials. Sort, filter, and save events you plan to attend.' (replaces the SBHA-only phrasing)
- listEvents API extended with associationCodes[] (legacy associationCode still accepted)
Clubs Page (Phase A4)
- Association single-select dropdown replaced with multi-select toggle chip row
- Each chip Link encodes ?assoc=X&assoc=Y so URLs are bookmarkable + shareable
- Search bar preserves current assoc selections via hidden inputs
- 'Clear' chip when any associations are selected
Avatar Menu / User Admin (Phase A5)
- Reordered to match the SBHA deck: My Profile → My Registered Dogs → My Event Registrations → My Saved Itinerary → Dogs I Follow → Saved Searches → Club Admin
- New /me/dogs page — focused list of every dog you own with quick title/result counts and a Register-a-Dog button
- 'My Registrations' renamed to 'My Event Registrations' for clarity
- 'My Itinerary' renamed to 'My Saved Itinerary'
Home Page Refresh: My Field Trial Planner
Phase A2 of the SBHA demo change-request batch. New headline, new paragraph copy, Chris Mathan hero photo with proper credit, and 'Stakes We Track' replaces 'Sports We Track' with eight bird-dog field-trial stake categories that link to a filtered event list.
Hero & Copy
- Headline: 'MY FIELD TRIAL PLANNER.' (replaces 'FIELD TRIAL REGISTRATION, DRAWS & CALENDAR.')
- Paragraph: 'A central place for owners/handlers to find American Field recognized Field Trials and Clubs, create dog profiles, save events to an itinerary, register and pay for events, see brace assignments, track a dog's points and titles and more.'
- Hero image: Chris Mathan pointer-on-point photo, right-aligned with crop space from left to fit the 3/4 portrait frame
- Hyperlinked photo credit caption ('Photo: Chris Mathan Sporting Dogs') overlaid in the bottom strip; opens chrismathansportingdogs.com in a new tab
- CSP img-src widened to allow image6.photobiz.com and chrismathansportingdogs.com
Stakes We Track
- Section renamed from 'Sports We Track' to 'Stakes We Track' (demo mode)
- Eight chips replacing the previous sport list: Puppy, Derby, Walking Shooting Dog, Horseback Shooting Dog, Shooting Dog Derby, All Age, Cover Dog, Gun Dog
- Each chip is now a clickable Link → /events?q={stake name} for a pre-filtered calendar
- Color-coded icons stay; the multi-sport list is preserved and still renders when DEMO_BIRD_DOG_ONLY is off
Search Enhancement
- Event search query now also matches stake names — 'Puppy', 'All Age', 'Walking Shooting Dog' all return events with that stake
- Implementation: listEvents pre-finds matching stakes, ORs their event IDs into the query result alongside event-name / club-name / grounds / city / state matching
- Benefits everyone — not just demo mode — so the change is unconditional
Bird-Dog Demo Mode
First batch of changes from the SBHA demo change-request review. Hides retriever and other non-bird-dog content behind a single feature flag (NEXT_PUBLIC_DEMO_BIRD_DOG_ONLY) — engines and data stay intact for later. Designed to be flipped back to the multi-sport build with one env var change + rebuild.
What's hidden in demo mode
- SRS Crown Championship and Purina High Point Retriever cards on dog profile, brag sheet, handler résumé, and compare page
- SRS / Purina High Point sections on /standings (Purina Pointing Dog Awards remain — those are bird-dog specific)
- Per-result SRS / Purina High Point chips in the dog profile competition table
- Hunt-test (JH/SH/MH) achievement badges and SRS / Purina High Point badges in the auto-derivation engine
- Sport tags in /feed filters, the post composer, and the post-edit popover collapse to just 'Field Trial'
- Sport options on the Add Result dialog collapse to 'Field Trial'
Breed list restricted to FDSB pedigreed bird dogs
- Three groups in the breed dropdown: Pointers/Setters, Pointing Breeds, European/Rare Breeds
- Pointers/Setters: English Pointer, English Setter, Gordon Setter, Irish Setter, Irish Red and White Setter, Llewellin Setter
- Pointing Breeds: Brittany (Epagneul Breton), German Shorthaired Pointer, German Wirehaired Pointer, Vizsla, Weimaraner, Wirehaired Pointing Griffon, Spinone Italiano, Wirehaired Vizsla
- European/Rare: Bracco Italiano, Braque d'Auvergne, Braque Francais, Large Munsterlander, Small Munsterlander, Pudelpointer, Old Danish Pointing Dog, Portuguese Pointer, Slovakian Wire-haired Pointing Dog
- Source: americanfield.com/article/breeds-receiving-fdsb-pedigrees
Implementation
- Single feature flag NEXT_PUBLIC_DEMO_BIRD_DOG_ONLY=1 controls everything — flip to 0 (or unset) and rebuild to restore multi-sport content
- All hidden surfaces are conditional renders, not deletions — the points engines (SRS, Purina High Point, Purina Pointing) stay in code and continue to compute correctly
- Existing dog rows that already have non-bird-dog breeds in the DB still display — only the dropdowns are restricted
- Existing posts with hidden sport tags still render their sport pill — only the picker is restricted
Public Community Feed
/feed is now publicly readable — share a link to a great training photo or a competition celebration without forcing the recipient to sign in. Posting, liking, and commenting still require an account; existing 'Sign in to comment / like / post' fallbacks already covered the guest experience.
Now Public
- /feed page is browsable to anyone — see posts, photos, captions, sport tags, dog tags, comment threads, like counts
- Post author + tagged dog names link through to handler / dog profiles (already public)
- Guest viewers see read-only state — likes show counts but heart can't be flipped, comments render but the composer prompts 'Sign in to comment'
Still Auth-Required
- Posting (POST /api/posts), editing (PUT /api/posts/[id]), deleting (DELETE /api/posts/[id])
- Liking and unliking (POST /api/posts/[id]/like)
- Commenting (POST /api/posts/[id]/comments) and deleting comments
SEO & Sharing
- Open Graph + Twitter card metadata so /feed previews nicely when shared (title, description, default site image)
- /feed added to /sitemap.xml at hourly change frequency
- robots.txt updated — /feed is now in the Allow list, removed from Disallow
Edit Litter Announcements
Completes the CRUD on litters that started with 2.22.0. Breeders can now flip status from Planned → Bred → Born → Available → Sold Out as the litter progresses, fix typos in the description, swap the photo, or correct the parent links — all without deleting and recreating.
Edit API
- PUT /api/litters/[id] — breeder-only, rate-limited 30/min/IP
- Same field validation as POST: status enum, parent sex match, date format, count bounds (0–30), text length caps
- After applying parent updates, server re-checks that each parent is still labelled (linked dog OR free-text name)
- Partial updates supported — send only the fields you're changing
Edit UI
- New /litters/[id]/edit page — breeder-only (returns 404 to non-breeders to hide existence)
- <LitterForm> refactored to a shared component used by both /litters/new and /litters/[id]/edit
- Pre-fills every field from the existing row, including resolving linked sire/dam display names from the dogs table
- Cancel returns to the litter detail page (or /litters from the new form)
- Submit copy adapts: 'Announce litter' on create, 'Save changes' on edit
Surfacing
- New 'Edit' button next to Delete in the litter detail page header (only renders when viewer is the breeder)
- Middleware now treats /litters/[id]/edit as a private route inside the public /litters prefix
Printable Running Order
Club secretaries and judges have been asking — the brace draw is now printable. Same letter-paper print stylesheet as the dog brag sheet (2.11.0), public, no login required, with page-break controls so each stake stays together on a single sheet when possible.
What's on the page
- Banner: 'Official Running Order' label, event name, date range, grounds, city/state
- Summary line: stake count, brace count, total entries
- One section per stake — name, participant type (Open/Amateur/Pro), category (Major/Minor/Derby/etc.), brace count
- Per-brace row: brace #, day, scheduled time (or TBD), course label (when set), Dog 1 + Handler 1, Dog 2 + Handler 2, BYE pill when applicable
- Generated timestamp + SportingDogs.app branding in the footer
Print Behavior
- @page letter-size with 0.4-inch margins
- @media print hides the site nav, header, footer, and the in-page Print buttons
- page-break-inside: avoid on each stake section so a stake doesn't split mid-table
- Print buttons at top and bottom of the paper for both screen reading and printing
Where to find it
- New 'Print' button at the top of every /events/[id]/braces running-order page
- Direct URL: /events/[id]/braces/print — bookmarkable, shareable, public
- Open Graph metadata so the print page also previews nicely when shared
- If the brace draw hasn't been run yet, the print page shows the same 'check back after the official drawing' message as the regular running-order page
Milestone Notifications: Placements & Litters
Quick consolidating release that hooks two more producers into the dog_milestone notification kind we shipped in 2.23. Following a dog now means you'll see their placements at field trials AND any litter announcement that names them as sire or dam.
Placement Milestones
- POST /api/dogs/[id]/results now writes a dog_milestone row for every follower (excluding the owner)
- Title: '{Dog name} placed {result} at {event name}'
- Body: stake · level · location (whichever are present), or the event date as fallback
- Link: deep-links to the dog's profile
Litter Milestones
- POST /api/litters now writes a dog_milestone row for every follower of the sire AND the dam (excluding the breeder)
- Each follower gets one row even if they happen to follow both parents (deduped per user)
- Title: '{Followed dog} {sired|whelped} a new litter'
- Body: litter name, or '{sire} × {dam} · {status}' if the litter has no name
- Link: deep-links to /litters/[id]
Why this matters
- Following 'that dog who keeps winning Open at Roxboro' is now actually useful — you find out about every placement
- Breeders following a stud get pinged automatically when their go-to sire has a new litter announcement
- Owners of sire/dam dogs don't get duplicate notifications about their own litter (skipped on the producer side)
Follow Dogs You Care About
Star any dog on the platform — your kid's litter sire, a rival's standout performer, the dam of your next pup — and get a notification the moment they earn a new title. Foundation for following placements and litters in future releases.
Follow Dogs
- New Follow / Following toggle button on every dog profile (next to Brag Sheet)
- Filled gold star + 'Following' label when you've followed a dog
- Follower counter visible on the button (e.g. 'Follow (8)')
- Optimistic-flip UI — instant visual feedback, reverts if the request fails
- Guest viewers see a 'Sign in to follow' link that bounces to login with the right next= URL
My Follows
- New /me/follows page in the avatar menu — grid of every dog you follow
- Each card shows photo, registered name, breed, sex, owner, and quick title/results counts
- Click any card to jump to the full dog profile
New Notification Kind: dog_milestone
- When a followed dog earns a new title (POST /api/dogs/[id]/titles), every follower gets an inbox row
- Title: '{Dog name} earned a new title: {code}'
- Body: full title name, organization, and date
- Link: deep-link to the dog's profile
- Owner is excluded — they obviously already know
API & Data
- POST /api/dogs/[id]/follow — toggle endpoint, idempotent (calling twice ends up unfollowed). Returns { following, count }
- Rate-limited at 60 toggles per IP per minute
- New `dog_follows` table (user_id, dog_id, created_at) with a UNIQUE INDEX on (user_id, dog_id) preventing duplicate follow rows
- Indexed on user_id and dog_id for fast inbox + follower lookups
- Migration 0009 applied to production
Litter Announcements
Breeders can now announce upcoming, expected, born, available, and sold-out litters tied to a registered sire and dam (or free-text names if a parent isn't on the platform). Each announcement is public — shareable to puppy buyers, breed clubs, and whoever asks. Connects the pedigree work from 2.8 to a real-world breeding workflow.
New /litters Section
- Public /litters index — grid of every announcement, status pill (Planned / Bred / Born / Available / Sold Out), photo, sire × dam, expected/born date, breeder name
- Public /litters/[id] detail — full description, breeder contact (email shown to signed-in viewers, 'Sign in to contact' for guests), male/female counts, photo banner
- /litters/new form (auth) — DogPicker autocomplete for sire (Male) and dam (Female), with free-text fallback for parents not on the platform
- Status, expected date, born date, male/female counts, description (4000 char), litter photo (existing FileUpload kind), and a contact note
- New 'Litters' tab in the desktop and mobile nav (Bone icon)
Pedigree Tie-In
- Each registered parent dog now shows a 'Litters' section on their profile when announcements reference them
- Cards label 'Sired litter' or 'Whelped litter' depending on which parent the dog is
- Status pill + born/expected date per card; click to read the full announcement
API & Validation
- POST /api/litters — auth required, rate-limited 10/min/IP
- Sire and dam must be labelled (linked dog or free-text name) — at least one of each
- If linked, server verifies the dog exists and the sex matches (sire must be Male, dam must be Female)
- status enum enforced server-side; date fields validated; counts capped 0–30
- DELETE /api/litters/[id] — breeder-only
Data & Discoverability
- New `litters` table with FK refs to dogs (ON DELETE SET NULL — deleting a dog doesn't wipe the announcement, just the link)
- Indexed on breeder, sire, dam, status
- Migration 0008 applied to production
- Litter URLs included in /sitemap.xml and Open Graph card metadata
- Public route added to middleware (auth still required for /litters/new)
Edit Posts In Place
Quick follow-on to 2.20.0 — fix that typo, retag the right dog, or change the sport label without deleting and reposting. Inline pencil-icon popover on /feed posts you own; saves with one click.
Edit API
- PUT /api/posts/[id] — author-only, rate-limited 30/min/IP
- Editable: caption, dogId, sport
- Not editable here: media (re-uploading would be a new feature)
- Server enforces 'caption can't be empty when there's no media'
- dogId must be a dog you own (returns 403 otherwise — same rule as create)
- sport restricted to the same allow-list as create
Inline Editor
- Pencil icon next to the trash on your own posts
- Click → small popover with textarea + dog/sport dropdowns
- Cancel restores the original values; Save flips back to read mode and refreshes the feed
- <article> elements are now id'd as #post-{id} so the comment_reply notification deep-link from 2.19 works
Create & Delete Posts
The community feed is finally writable. Inline composer at the top of /feed lets you share a caption, attach a photo, tag one of your dogs, and label it with a sport. Authors can delete their own posts (likes and comments cascade). Closes the loop on the social loop started in 2.19.
Inline Composer
- Collapsed state: a single 'Share an update…' input next to your avatar — minimal noise on the feed
- Expanded state: textarea + photo upload + dog tag dropdown (your dogs only) + sport tag
- Photo upload uses the existing 'post-media' upload kind so files land in the same /uploads/ tree as before
- Caption max 4,000 chars; mediaUrl validated; sport restricted to the same allow-list used in the filter chips
- Cancel restores the collapsed state and clears all fields
- Send button disabled until you have a caption or media
API
- POST /api/posts — auth required, rate-limited 20/min/IP
- Either caption OR mediaUrl required (or both)
- dogId only accepted if you own the dog (returns 403 otherwise — no tagging other people's dogs)
- DELETE /api/posts/[id] — author-only; likes and comments cascade via FK
- type column derived server-side: 'video' | 'photo' | 'text'
UI Polish
- Trash icon next to your own posts (with confirm-Yes/No flow before deleting)
- Author name and tagged dog now link to /handlers/[id] and /dogs/[id]
- Guest viewers see a 'Sign in' callout where the composer would be
Likes, Comments & Reply Notifications
The /feed buttons that have looked clickable since launch are finally wired up. Real likes, real comments, and the notification kind we declared back in 2.16 — comment_reply — now actually fires when someone replies to your post.
Likes
- POST /api/posts/[id]/like — toggle endpoint, idempotent (calling twice ends up unliked)
- Returns { liked, count } so the client can render exact post-toggle state
- Optimistic UI on /feed — heart fills + counter bumps before the network round-trip resolves; reverts on failure
- Hearts are red and filled when liked, outlined and muted when not
- Rate-limited at 60 toggles per IP per minute
Comments
- POST /api/posts/[id]/comments — create a comment (text required, max 2000 chars)
- DELETE /api/posts/[id]/comments/[commentId] — author OR post-owner can delete
- /feed gets a real comment composer below each post (Send icon button, focus-ring style)
- Commenter avatars + names render with handler-profile links
- Trash icon on hover for comments you can delete
- View-all expander when there are more than 2 comments
- Rate-limited at 30 comments per IP per minute
Comment Reply Notifications
- When you comment on someone else's post, a comment_reply notification lands in their inbox
- Title: '{your name} commented on your post'
- Body: a 120-char preview of your comment
- Link: deep-link to the post (/feed#post-{id})
- Self-comments are skipped (no self-pings)
Auth Behavior
- Guest viewers see read-only likes and comments + a 'Sign in to comment' link
- Click a heart while signed out → bounced to /auth/login?next=/feed
Saved-Search Hits Hit the Inbox
The hourly saved-search digest cron now writes a notification row alongside every email it sends, so the bell icon lights up the moment new matching events get posted. Quick follow-on to the 2.16 inbox foundation — more producers means the inbox actually gets used.
New Producer
- Hourly cron at /api/cron/saved-search-digest now calls createNotification() for every saved-search match
- Notification kind: saved_search_hit (renders with the Search icon in the inbox)
- Title: '{N} new event{s} match "{search name}"'
- Body: lists up to the first three event names plus '+N more' if there are additional matches
- Link: deep-links to /events?{filters} so one click reruns the search
Why this matters
- Email is great for week-aware planning, but the bell badge is what handlers check between sessions
- No double-emailing — the cron's idempotent watermark (lastNotifiedAt) still controls whether the digest fires; both email and notification are sent in the same window
- Saved searches without an email frequency still receive the inbox row whenever the cron runs (since the watermark moves regardless)
Coming next
- Comment-reply notifications (need a comment-write API first)
- Like notifications (probably opt-in to avoid noise)
- Cron-job summary for super-admins
Achievement Badges
Auto-derived achievement chips on every dog profile and brag sheet. Pure derivations from existing titles, results, health clearances, and offspring — nothing new to enter, surprises pop up the moment you add a qualifying placement or title.
Title Badges
- Hunt Test ladder: Junior Hunter / Senior Hunter / Master Hunter (only the highest tier shows)
- Field Trial: Field Champion (FC), Amateur Field Champion (AFC), National Field Champion (NFC)
- Dual Champion — when a dog carries both FC and AFC
- NAVHDA: Versatile Champion (VC), UT Prize I
- 10× Titled badge for prolific competitors
Performance Badges
- 100 Runs Club / 25+ Competitions — total competition volume
- Qualifying Runs counter — total qualifying placements
- Consistent Performer — 80%+ qualify rate over 10+ runs
- SRS Points threshold (50+)
- Purina High Point thresholds (25+, 100+)
Health & Pedigree Badges
- OFA Hip Excellent
- Health Clearances counter (4+)
- Champion Sire / Champion Dam — 3+ titled offspring on the platform
- Prolific parent — 5+ offspring registered (when titled-offspring threshold isn't met)
Where they show up
- Top of every dog profile, right under the header
- Dedicated 'Achievements' section on the printable brag sheet
- Color-coded by tone: amber (titles/SRS), red (Purina), emerald (health), blue/violet (dam/sire), slate (volume)
- Hover any chip for the explanation behind it
Notifications Inbox
An in-app inbox at /me/notifications, a bell icon in the nav with a live unread badge, and the first producer wired up: event registrations now drop a confirmation row when an entry goes through (free, manual, or paid via Stripe). Foundation for saved-search-hit and comment-reply notifications in future releases.
Inbox
- New /me/notifications page — shows the 100 most recent notifications, grouped chronologically
- Unread rows are highlighted with a primary-color border and a small dot
- Each row: kind icon (Trophy / Search / MessageCircle / Sparkles), title, optional body, time-ago, deep link, and a dismiss button
- 'Mark all read' button at the top, disabled when there's nothing unread
- Empty-state when the inbox is clean
Bell Icon
- New bell button in the nav header (left of the avatar menu)
- Red badge with unread count — '99+' if the queue runs deep
- Polls /api/me/notifications?unread=1 every 60 seconds while signed in
- One-click jump to the inbox
API
- GET /api/me/notifications?unread=1&limit=N&since=ISO — paginated, returns rows + unread count
- POST /api/me/notifications/read-all — flips every unread row to read in one round trip
- PATCH /api/me/notifications/[id] — mark a single row read
- DELETE /api/me/notifications/[id] — dismiss a row
- All endpoints owner-scoped; cross-user access returns nothing
Producers Wired Up
- POST /api/events/[id]/register inserts a row immediately after a free or manual registration is recorded
- Stripe webhook inserts a 'Payment received' row when payment_intent.succeeded fires, in parallel with the existing email confirmation
- All inserts are fire-and-forget — if the notification fails to write, the originating action still succeeds
Schema
- New `notifications` table (id, user_id, kind, title, body, link, read_at, created_at)
- Indexed on (user_id, created_at) and (user_id, read_at) for fast inbox + unread-count lookups
- Migration 0007 applied to production
Handler Career Résumé
Mirror of the dog brag sheet, but for handlers — print-friendly career résumé aggregating every dog you own. Avatar + bio banner, career snapshot across all dogs combined, a card for each dog with their own SRS/Purina chips, and the 15 most recent results across the whole kennel.
Handler Brag Sheet
- New /handlers/[id]/brag page — public, server-rendered, mirrors the dog brag sheet pattern from 2.11.0
- Banner: avatar (or initials), name, location, contact (or 'Sign in to contact' for guests), bio
- Career snapshot: Dogs Owned, Titles Earned, Competitions, Qualify Rate, SRS Crown total, Purina High Point total. Purina Pointing total appears below when non-zero
- Per-dog grid: name, breed, sex glyph, plus inline chips for titles count, results count, SRS, Purina
- Recent results table (last 15 across all dogs) with dog name, event, stake, result, and computed point chip
- Letter-paper print stylesheet — same one as the dog brag sheet
Surfacing
- New 'Career Résumé' button on every handler profile, top-right of the banner
- Open Graph + Twitter card metadata using the handler's avatar (or fallback) when shared
- Sitemap now includes /handlers/[id]/brag for every handler
Why this matters
- Hand a print-out (or share a link) to a sponsor, club secretary, or judge
- Easy to compare two handlers' careers side by side via two URLs
- Public — no sign-in required to view or print
Site-wide Search
One search box, four kinds of results: dogs, handlers, clubs, events. Lives in the desktop nav and the mobile hamburger, returns categorized results on /search, and is publicly accessible (no login required) so guests can find their way around too.
Search Page
- New /search?q=... page — server-rendered, public, sectioned by entity type
- Dogs section: photo, registered name, breed, sex — links to dog profile
- Handlers section: avatar/initials, name, location — links to handler profile
- Clubs section: name + location — links to club page
- Events section: name, date range, grounds/city/state — links to event detail
- Each section caps at 12 results to keep the page fast and scannable
Nav Search Box
- New search input in the desktop nav (between the Help dropdown and Add Dog)
- Mobile hamburger menu also gets a full-width search input at the top
- Submitting either form takes you to /search?q=... — bookmarkable, shareable
API
- GET /api/search?q=...&limit=N — public, no auth required
- Returns { dogs, handlers, clubs, events, counts } categorized
- LIKE-based partial matching on names, registered names, club locations, event grounds and city
- Per-category cap (default 8, max 20)
- Rejects queries shorter than 2 chars to avoid scanning the whole DB
SEO, Sitemap & Social Sharing
Pasting a dog profile, brag sheet, event page, club page, or handler résumé into iMessage, Slack, Facebook, or Twitter now produces a real preview card with the dog's name, photo, and stats. Search engines can finally crawl the public catalog via /sitemap.xml and /robots.txt.
Open Graph & Twitter Cards
- Default OG and Twitter card metadata in the root layout (icon-512 image, summary_large_image card)
- /dogs/[id] uses the dog's photo, registered name, breed, and owner in the card
- /dogs/[id]/brag uses the dog's photo with a 'Brag Sheet' suffix and a stat-rich description
- /handlers/[id] uses the handler's avatar, name, location, and dog count
- /events/[id] shows event name, dates, and venue
- /clubs/[id] shows club name and bio (or a generated description)
- All pages inherit a sensible SportingDogs.app fallback if a specific image isn't available
Sitemap & robots.txt
- /sitemap.xml — Next.js MetadataRoute.Sitemap, enumerates static pages plus rows for every dog, handler, club, and event (capped at 5,000 each)
- Per-URL <changeFrequency> and <priority> tuned to update cadence: events daily, dogs weekly, docs monthly
- /robots.txt — explicitly allows public read pages, disallows /api/, /admin/, /me/, and edit/register sub-routes
- Sitemap referenced from robots.txt so crawlers can discover it
Why this matters
- Sharing a brag sheet to a breeder group chat now produces a rich preview, not a bare URL
- Google can index the public catalog — handlers searching for 'their dog name SBHA' should start finding profiles
- Backlinks to clubs and events are no longer broken or anonymous when shared
Public Read-Only Access
Field trial calendars, dog profiles, brag sheets, handler résumés, club pages, standings, and stats are now visible to anyone — no login required. Members still need to sign in to register, post, edit, or see contact info. Share a link with a judge, breeder, or prospective puppy buyer and it just works.
Now Public
- Home (/), docs, and What's New
- /dogs index, /dogs/[id] profile, /dogs/[id]/pedigree, /dogs/[id]/brag, /dogs/compare
- /handlers/[id] handler résumés (email is hidden for guests, replaced with a 'Sign in to contact' link)
- /clubs, /clubs/[id] club pages
- /events, /events/[id], /events/[id]/braces, /events/[id]/results, /events/calendar
- /standings season standings and /stats league-wide stats
Still Auth-Required
- Registering for events (/events/[id]/register)
- Saving events to your itinerary (/events/saved) and saved searches
- Creating or editing a dog (/dogs/new, /dogs/[id]/edit) and the training journal
- Viewing your registrations or profile (/registrations, /profile)
- All admin pages (/admin/*)
- All write APIs (POST, PUT, DELETE on /api/*)
Privacy
- Handler email is shown only to authenticated viewers — guests see a 'Sign in to contact' CTA
- Training journals stay strictly owner-only as before
- User-generated content (posts, comments) still lives on the auth-gated /feed for now
- Site nav now shows both 'Sign In' and a 'Sign up free' button for guests
Printable Dog Brag Sheet
A clean one-page résumé for any dog — banner photo, full titled name, vital stats, career snapshot, full title list, health clearances, pedigree, and recent results. Tuned for letter-size printing or save-as-PDF, with all the site chrome stripped out.
What's on the brag sheet
- Banner with the dog's photo, full titled name (prefix titles + registered name + suffix titles), call name, breed, sex, age, height/weight, color, registration org/number, and owner
- Career snapshot: Titles count, Competitions count, Qualify Rate, SRS Crown total, Purina High Point, Purina Pointing total
- Full title list as a table (code, name, organization, sport, date)
- Full health clearances table (type, result, organization, date, certificate number)
- Pedigree section showing sire, dam, and offspring names
- Recent competition results (up to 8 most recent) with dates, event, stake, result, and points
- Generated date + SportingDogs.app footer for credibility when shared
Printing & Sharing
- New /dogs/[id]/brag route — server-rendered, deterministic, shareable
- 'Brag Sheet' button on every dog profile (next to Compare and Edit)
- Print / Save as PDF buttons at the top and bottom of the sheet
- Print stylesheet hides nav, header, and footer chrome — just the paper
- @page letter-size with 0.4-inch margins
- page-break-inside: avoid on each section so titles, health, and results don't split mid-table
Use cases
- Hand to a judge before a stake
- Share with a breeder considering a stud match
- Email to a prospective puppy buyer along with the dam's brag sheet
- Quick reference at the trial when chatting with another handler
Compare Dogs Side by Side
Stack up to 4 dogs in a single view: photos, basics, titles, health clearances, qualify rate, SRS Crown points, Purina High Point, and Purina Pointing totals — all rendered as columns so you can scan across at a glance. Useful for breeding decisions, picking a stud, or just bragging.
Compare Page
- New /dogs/compare page accepting up to 4 dog IDs via the ?ids= URL parameter
- Photo header card per dog with name, registered name, and a quick remove (X) control
- Comparison rows: Sex, Breed, Age, Height, Weight, Owner, Titles (with up to 3 chips), Health Clearances, Total Results, Qualify Rate, SRS Crown, Purina High Point, Purina Pointing
- Section dividers separate Achievements vs Competition rows
Adding & Removing Dogs
- Picker at the bottom uses the same DogPicker autocomplete shipped in 2.8.0
- Maximum of 4 dogs at a time — extra picks ignored
- Removing the last dog returns you to the empty-state with the picker primed
- URL state means comparisons are bookmarkable and shareable
Surfacing
- New 'Compare' button in the /dogs header (next to Register Dog)
- New 'Compare' button in every dog profile header (right next to Edit) — preloads the URL with that dog as the first column
4-Generation Pedigree Tree View
The classic horse-pedigree grid: subject on the left, parents, grandparents, and great-grandparents fanning out to the right. Linked names are clickable so you can pivot anywhere in the family tree. Builds directly on 2.8.0's pedigree linking.
Pedigree Tree
- New /dogs/[id]/pedigree page with a 4-generation tree (subject + parents + grandparents + great-grandparents)
- Sire-side cells get a blue accent border, dam-side cells get a pink one — matches breed-conventional pedigree colors
- Each cell shows registered name (or call name), sex glyph, breed, and birth year
- Empty cells show 'Unknown parent / grandparent / ancestor' so you know what's missing
- Free-text-only ancestors (sire/dam name without a linked dog) render with an 'not on platform' note
- Fully responsive: classic 4-column grid on desktop, linear list on mobile
Navigation
- New 'View 4-Gen Tree' link in the Pedigree section of every dog profile
- Every name in the tree links to the corresponding dog's profile (when registered)
- Click a great-grandparent → land on their profile → click their pedigree → walk back further generations
Data Integrity
- Cycle detection: if a dog appears twice in its own ancestry chain (shouldn't happen, but defensive code), the inner cell renders without re-walking
- Deleted parent IDs are tolerated — fall back to free-text name when present
- All in-memory pedigree walking; no extra DB hits beyond the existing dog snapshot
Pedigree Linking
Sire and Dam fields now autocomplete against every dog registered on the platform. Pick from the dropdown and the parent becomes a real link — click through to see the parent's profile, and the parent's profile shows their offspring back. Free-text still works for sires and dams not on the site.
Pedigree Autocomplete
- Sire and Dam fields on /dogs/new and /dogs/[id]/edit now search the dogs table as you type
- Suggestions filter by sex automatically (Sire → Male only, Dam → Female only)
- Excludes the dog being edited so you can't pick yourself as your own parent
- Picking a suggestion 'links' the field — visually highlighted with a check icon and an unlink button
- Free-text fallback still works when the parent isn't registered on the platform
Clickable Pedigree on the Dog Profile
- Sire and Dam render as links when they point to a registered dog
- New 'Offspring' section lists every child whose sireId or damId points to this dog
- Each offspring chip shows their sex (♂/♀) and links to their profile
- Building blocks for a full multi-generation pedigree tree in a future release
API & Data
- New columns dogs.sire_id and dogs.dam_id (nullable varchar(64))
- POST /api/dogs and PUT /api/dogs/[id] validate parent links: parent must exist, sex must match, and a dog can't be its own parent
- GET /api/dogs/search?q=...&exclude=...&sex=Male|Female — auth-only autocomplete (max 10 results, min 2 chars)
- Migration 0006 applied to production
Private Training Journal
Log training sessions per dog — what you worked on, where, how long, and the conditions. Entries are owner-private (no one but you can see them) so you can be honest about wins and struggles. Big leap toward making the platform genuinely useful between events, not just at them.
Training Journal
- New 'Training Journal' section on the dog profile (visible only to the owner)
- Log entries: date, session type (Yard / Marks / Blinds / Field / Conditioning / Bird Work / Obedience / Vet / Other), duration, weather, location, free-form notes
- Up to 4000-character notes per entry — room for real reflection
- Most recent 50 entries shown, sorted newest first
- Inline trash icon on each entry for quick removal
- Privacy-pill makes it obvious entries aren't public
API
- POST /api/dogs/[id]/training — owner-only, validates date (no future dates), session type, duration (0–1440 min), and notes length
- DELETE /api/dogs/[id]/training/[entryId] — owner-only, scoped to (dog, owner)
- Rate-limited at 60 entries/min per IP to keep abuse low while allowing legitimate quick logging
Schema
- New `training_log` table with indexes on (dog_id) and (owner_id, date) for fast lookups
- Migration 0005 applied to production
Inline Title & Health Clearance Management
Owners can now add and remove titles and health clearances directly from the dog profile — no more waiting on data imports. Each section gets an 'Add' button (with smart presets) and inline delete controls.
Add & Remove Titles
- New 'Add Title' dialog on the dog profile (owner only)
- Quick-pick chips for common titles: JH, SH, MH, NA Prize I, UT Prize I, VC, FC, AFC, NFC, MACH
- Auto-fills full name, sport, and prefix/suffix flag when you pick a preset
- Custom titles supported — manually enter code, name, organization, sport, and date
- Inline trash icon next to each title for quick removal (with confirm)
- POST /api/dogs/[id]/titles, DELETE /api/dogs/[id]/titles/[titleId] — owner-only
Add & Remove Health Clearances
- New 'Add Clearance' dialog with type-aware result dropdowns
- Hip → Excellent / Good / Fair / Borderline / Mild / Moderate / Severe
- Elbow → Normal / Grade I / II / III; Eye/Cardiac/Patella/Thyroid → Clear / Affected / Carrier
- DNA panels (EIC, DM, PRA, CNM) → Clear / Carrier / Affected
- Optional certificate number and expiration date
- Inline delete on each clearance row
- POST /api/dogs/[id]/health, DELETE /api/dogs/[id]/health/[hcId] — owner-only
Owner Controls Visible Only When Logged In
- Add buttons & trash icons only render when the session user is the dog's owner
- Empty Titles / Health Clearances sections still show for owners (with the Add button) so adding the first item is one click
- Visitors see the same clean read-only profile as before
Edit Dogs & Public Handler Profiles
Two crosslink-heavy additions: handlers can now edit (and delete) their own dogs, and every handler name on the platform now links to a public profile page that aggregates their dogs and career results.
Edit & Delete Dogs
- New /dogs/[id]/edit page with the full registration form pre-populated
- PUT /api/dogs/[id] — owner-only update with validation (height 1–60, weight 1–500, DOB not in future, allowed breed groups, allowed sex)
- DELETE /api/dogs/[id] — owner-only delete, blocked when there are registrations or competition results on file
- 'Edit' button on the dog profile page (owner only)
- Photo upload in edit mode replaces the current photo
Public Handler Profiles
- New /handlers/[id] page with handler avatar, location, bio, and email
- Career stats: total dogs, total titles, combined SRS Crown points, combined Purina total
- Per-dog grid linking to each dog's profile, with quick stats (titles, results count)
- Recent results table (last 15 across all owned dogs) with placement badges and earned points
Crosslinks
- Handler names on event running-order (brace draws) now link to /handlers/[id]
- Handler column on event results page links to /handlers/[id]
- Owner name on dog profile links to /handlers/[id]
- Makes it easy to jump from a placement → handler → their other dogs → those dogs' results
Form Refactor
- Dog registration and edit share a single <DogForm /> component
- Submit calls /api/dogs (POST) for create or /api/dogs/[id] (PUT) for edit
- Cancel returns to wherever you came from
Saved Searches & Help Menu
Subscribe to filter combinations on the events page and get notified when new matching events are posted. Plus housekeeping: dropdown Help menu (Documentation + What's New), club editing, dog photo uploads, and a real dog registration form.
Saved Searches & Email Subscriptions
- New 'Save Search' button on /events captures the current filter combination
- Pick frequency: As they're posted (within an hour), Daily, Weekly, or save without emails
- New /me/saved-searches page lists every subscription with last-notified date
- Edit frequency or delete searches from the same page
- Hourly cron sends digest emails for new matching events; idempotent so retries don't double-send
- Saved-searches link added to the avatar menu
Navigation & Layout
- Removed standalone Home nav item — the logo is now the home link
- Documentation and What's New consolidated into a single Help dropdown to free up nav real estate
- Mobile hamburger menu also groups Help items under their own section
Club Editing
- Club details (name, contact, website, location, description) can be edited from /admin/clubs/[id]
- PUT /api/admin/clubs/[id] with validation; DELETE blocked when events still exist
Dog Registration
- Replaced the placeholder /dogs/new form with a real one
- Photo upload via drag-and-drop or file picker (saved to /uploads/dog-photo/)
- POST /api/dogs validates breed, breed group, sex, DOB, height/weight ranges
- Submit creates the dog in MySQL, redirects to its profile
Public Clubs Index & Dog Results History
Two new public discovery surfaces — the full clubs directory and a per-dog results table — so handlers can browse clubs by upcoming events and review every result a dog has logged across the platform.
Public Clubs Index
- New /clubs page lists every host club with name, location, association badges, and upcoming event count
- Sort: clubs with upcoming events surface first, alphabetical within each tier
- Search by club name, city, or state
- Filter by association (SBHA, NBHA, AFTCA, ABHA, USCSDA, NGSPA, NRSFTC, NVA)
- Each card shows the next upcoming event date + name and links to the full club detail page
Public Club Detail Pages
- New /clubs/[id] page with club bio, contact email, website link
- Upcoming events section sorted by date with stake count and entry-fee range
- Past events section showing completed trials (last 10)
- Active club admin roster (names + roles) so handlers know who to contact
- Club admins see an 'Edit in admin' shortcut linking back to /admin/clubs/[id]
Dog Results History
- New /dogs/[id]/results page: full chronological table of every competition result for a dog
- Sortable by date, sport, placement, points
- Aggregated career totals (titles count, total SRS Crown points, total Purina points)
- Filter by year, sport, qualifying-only
- Linked from the dog profile page above the embedded recent-results section
Standings, Search & Self-Serve Admin
Post-event results loop closed: placements + earned points show on the public results page, a season-long Standings dashboard ranks every dog by SRS Crown and Purina, and clubs can invite their own admins from the UI.
Public Results Page
- New /events/[id]/results page with per-stake placements (1st through JAM), points earned, and direct links to each dog's profile
- Placement entry UI: secretaries click 'Record' next to a registration, pick placement, save — auto-creates the competition_results row
- Three points engines surfaced on every placed entry: SRS Crown (amber), Purina Retriever (red), Purina Pointing-Dog (green)
- Public link added to event detail page next to 'Running Order'
Season Standings Dashboard
- New /standings page aggregates competition results across every event
- SRS Crown Championship leaderboard (top 25 dogs by total points)
- Purina Top Open / Amateur / Derby Dog leaderboards (retriever)
- Purina Pointing-Dog Top All-Age / Shooting Dog / Derby leaderboards
- Each row links to the dog's profile with breed, owner, point breakdown
Multi-Day Brace Scheduling
- Inline 'Edit' on each brace row in the running-order view
- Secretaries change day (1-14), scheduled time, course label without leaving the page
- PUT /api/braces/[id] with full validation; DELETE for one-off corrections
- Public running order shows day/time/course as soon as it's set
Event Search & Discovery
- Free-text search bar above the events filter drawer
- Matches event name, club name, grounds, city, state in one query
- Persists in ?q= URL so search results are shareable
- Region groupings filter (Northeast / Southeast / Midwest / South / West / Plains)
Self-Serve Club Admin Invites
- Add admin form on /admin/clubs/[id] — type email, pick role, click Add
- Roles: secretary, chairman, trustee
- Existing admins can invite co-admins (no super-admin required)
- Last-admin lockout protection: can't remove yourself if you're the only admin
Real SBHA Events
- Pre-seeded 33 real upcoming events scraped from southernbirdhunters.org
- 25 host clubs auto-created and tagged with the SBHA association
- 93 default stakes (Open Shooting Dog, Amateur Shooting Dog, Derby) ready for secretaries to refine
- Idempotent re-run via scripts/seed-sbha-events.ts
Deploy Reliability
- NODE_OPTIONS=--max-old-space-size=768 caps build memory so the 1 GB droplet doesn't OOM-kill mid-build
- Disk pressure fix: KEEP_RELEASES dropped from 10 to 3 (each release dir is ~1 GB)
- Local-build → tarball → scp → atomic symlink swap pattern documented as fallback
- Critical security fixes: /api/events/[id]/draw and /api/registrations/[id]/result now gate on canManageEvent (was open to any logged-in user)
Club Admin & Event Management
Phase 5 brings a full club-secretary admin experience, file uploads, finer-grained brace draws, automated entry-close reminders, and a month-grid calendar view.
Club Admin
- New /admin dashboard for super-admins (manage all clubs) and per-club secretaries (manage their own club only)
- Per-club page at /admin/clubs/[id] with all events, stats, and the club's admin roster
- Membership driven by the club_admins table — secretaries are scoped to their own club's data
- Recent events list with registration counts surfaced on the dashboard
Event Management
- Create new events at /admin/events/new with inline stake editor (no second-screen round-trip)
- Edit existing events, stakes, and event metadata at /admin/events/[id]
- Per-stake registration page at /admin/events/[id]/registrations with handler list, status badges, and total revenue
- One-click CSV export of registrations for offline workflows
File Uploads
- New POST /api/uploads endpoint accepting kind=event-ad | dog-photo | post-media
- Reusable <FileUpload /> component with click-to-upload and drag-and-drop
- Event ad PDFs (or JPG/PNG) up to 10 MB attached directly to events
- Dog photos accept JPG/PNG/WebP up to 5 MB
- Files served from /uploads/... (nginx alias to /var/lib/sportingdogs/uploads/)
Brace Draws
- Stake-level draws via POST /api/stakes/[id]/draw — secretaries can draw one stake at a time as entries finalize
- Existing event-wide draw still available for clubs that prefer to draw all stakes together
- Re-running a stake's draw replaces only that stake's running order
Entry Reminders
- Automated reminder emails at 7 days, 24 hours, and 2 hours before entry-close
- Only fires for events you've saved but haven't registered for — no nags once you've entered
- Hourly systemd timer hits POST /api/cron/reminders (auth via x-cron-token)
- Idempotent via the sent_reminders table — no duplicate emails on retry
Calendar View
- New month-grid calendar at /events/calendar
- Prev/next navigation by month
- Saved events highlighted in yellow so your itinerary stands out at a glance
- Click any event tile to jump straight to its detail page
Field Trial Platform — built for SBHA
Major release adding the SBHA-aligned field trial domain: calendar, registration & payment, brace draws, and TKG branding. Implementation maps to the three pain points from The Keer Group's exploration deck.
Field Trial Calendar
- Browse all SBHA-recognized and partner-association field trials
- Filter by date range, US state, association (SBHA, NBHA, AFTCA, ABHA, USCSDA, NGSPA, NRSFTC, NVA), participant type (Open/Amateur/Pro), and event type (Puppy, Derby, Shooting Dog, Walking, Horseback, All Age, Cover Dog, Gun Dog, Youth)
- Geo-radius filter: enter a ZIP or use browser geolocation, see events within 50–1000 mi
- Distance-to-event displayed when location filter active
- Save events to a personal itinerary (My Itinerary page)
- Each event shows host club, grounds, drawing/entry-close timestamps, Purina Points / Eukanuba sponsor flags
Registration & Payment
- Per-stake dog picker with built-in eligibility check (age window enforced from stake.minAgeMonths/maxAgeMonths)
- Ownership validation (handlers can only register their own dogs)
- Duplicate prevention (a dog can't enter the same stake twice)
- Stripe Checkout integration for entry-fee payment
- Confirmation email with stake list, dog, total paid (via AWS SES)
- Free entries (Youth Stake = $0) auto-confirmed
- Manual-payment fallback when Stripe isn't yet configured for a club
- My Registrations page with status badges: Pending / Confirmed / Withdrawn / Scratched / Completed
Brace Draws & Running Order
- Club-secretary 'Run Brace Draw' button on event detail page
- Fisher-Yates random draw across all stakes; seed-able for reproducibility
- Bye-brace handling for odd-count stakes
- Public running order page at /events/[id]/braces with brace #, day, time, both dogs, handler names
- Re-run draw replaces the previous (with confirmation)
Post-event Results Entry
- Club secretary records placement (1st/2nd/3rd/4th/JAM/RJ/Pass/Fail/Scratched) per registration
- Auto-creates a competition_result row linked to the dog
- Feeds the SRS Crown and Purina Points engines automatically
- Registration status flips to 'Completed' on placement entry
Branding & Theme
- Refreshed color palette: TKG orange + navy charcoal + cream backgrounds matching The Keer Group deck
- Two-column hero with deck-style orange frame
- PWA theme color updated for installed-app branding
- All 8 association codes prominently displayed
Initial Launch
Welcome to SportingDogs! The platform for tracking, training, and competing with your sporting dogs.
Dog Registration
- Register dogs with full breed, pedigree, and registration details
- Upload dog photos
- Track height, weight, color, sire/dam info
- Support for all 32+ AKC Sporting Group breeds
- Breed group categorization (Retriever, Spaniel, Setter, Pointer/Versatile)
Competition Tracking
- Log results for Hunt Tests (AKC — JH, SH, MH levels)
- Log results for Field Trials (Retriever, Pointing, Spaniel)
- Log NAVHDA tests (NA, UPT, UT with Prize levels)
- Log Dock Diving results (NADD/DockDogs — distance, air retrieve, speed)
- Log Agility trial results (scores, times, championship points)
- Log Rally, Flyball, and Barn Hunt results
- Per-category scoring support (0-10 AKC, 0-4 NAVHDA)
Titles & Achievements
- Track titles across all major organizations (AKC, NAVHDA, NADD, NAFA, BHA)
- Prefix and suffix title support (FC, AFC, MACH vs JH, SH, MH)
- Title history with date earned
Health Clearances
- OFA Hip & Elbow evaluations with grades
- PennHIP Distraction Index tracking
- CAER (eye) certifications with expiration dates
- Cardiac evaluations
- DNA/Genetic test results (EIC, CNM, PRA-prcd, DM, etc.)
- OFA certification number tracking
Social Feed
- Share photos and videos of your dogs
- Tag dogs and sport types in posts
- Like and comment on community posts
- Sport-based filtering (Hunt Test, Dock Diving, Agility, etc.)
Stats & Leaderboards
- Breed distribution visualization
- Results breakdown by sport with qualifying rates
- Most titled dogs leaderboard
- Dock diving distance leaderboard
- Agility championship points leaderboard
- Titles by breed comparison
Platform
- Progressive Web App (PWA) — installable on mobile and desktop
- Fully mobile responsive design
- Dark mode support
- Bottom tab navigation on mobile
- Demo data seeding for easy exploration