Thursday, May 21, 2026

Make the registrations CSV export faster for large events

Volleyball Elite Academy development update
Volleyball Elite Academy
Make the registrations CSV export faster for large events

Make the registrations CSV export faster for large events

Volleyball Elite Academy — Development Update • May 21, 2026

--- title: Make the registrations CSV export faster for large events ---

Make the registrations CSV export faster for large events

## What & Why (server/routes/coaches ~line 104) currently enriches each row by calling , (athlete) and (parent) one-at-a-time inside a . For an export of N registrations that's ~3N round-trips to the database. On a busy season this is the difference between a snapshot that downloads in a second vs. one that times out.

## Done looks like - The CSV route batch-loads events and people (e.g. one query for all distinct event IDs and one for all distinct person IDs) instead of looping per row - The new automated test in still passes - Manual export of a large event (hundreds of registrations) returns noticeably faster

## Relevant files - (CSV route ~line 104) - (, , )

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Verify per-court swimlanes in Day and Week views with an automated test

Volleyball Elite Academy development update
Volleyball Elite Academy
Verify per-court swimlanes in Day and Week views with an automated test

Verify per-court swimlanes in Day and Week views with an automated test

Volleyball Elite Academy — Development Update • May 21, 2026

--- title: Verify per-court swimlanes in Day and Week views with an automated test ---

Verify per-court swimlanes in Day and Week views with an automated test

## What & Why The Season Calendar now renders multi-court rentals as per-court swimlanes in both Day view (Task #1064) and Week view (Task #1077). There is no automated test that locks in this behaviour, so future refactors of could silently regress the layout — collapsing rentals back to a single column or hiding the "Free" / "Unassigned" indicators.

## Done looks like - An e2e test seeds a multi-court rental with at least one assigned-court session and one unassigned-court session that fall within both a Day view date and the same week - The test asserts the Day view renders , an unassigned row, and the "Free" placeholder for empty courts - The test asserts the Week view renders swimlanes inside the correct column, plus an unassigned row when applicable - A single-court rental and a non-rental event still render as a single block in Week view (no swimlane wrapper)

## Relevant files - (WeekView, DayView, RentalSwimlane, buildDayRenderItems) - (gymRentalId / rentalCourtCount payload)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Search and pick a person by name when force-linking pending Corsizio rows

Volleyball Elite Academy development update
Volleyball Elite Academy
Search and pick a person by name when force-linking pending Corsizio rows

Search and pick a person by name when force-linking pending Corsizio rows

Volleyball Elite Academy — Development Update • May 21, 2026

--- title: Search and pick a person by name when force-linking pending Corsizio rows ---

Search and pick a person by name when force-linking pending Corsizio rows

## What & Why The Pending Corsizio panel added in Task #1061 currently asks the admin to type an EV person ID to force-link a staged registration. That's clunky — admins don't memorize numeric IDs. A type-ahead person picker (by name/email) would make the action usable.

## Done looks like - The "Force-link" button opens a person picker that searches academy_people by name/email and shows top matches. - Selecting a result calls POST with that person's id. - Existing manual-link endpoint is unchanged.

## Relevant files - (PendingCorsizioPanel) - (existing person search endpoints)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Cover the new reconciliation screen with end-to-end tests

Volleyball Elite Academy development update
Volleyball Elite Academy
Cover the new reconciliation screen with end-to-end tests

Cover the new reconciliation screen with end-to-end tests

Volleyball Elite Academy — Development Update • May 21, 2026

--- title: Cover the new reconciliation screen with end-to-end tests ---

Cover the new reconciliation screen with end-to-end tests

## What & Why The Corsizio reconciliation screen ships with no e2e coverage — the existing System Overview entry for Corsizio still has (see replit "Coverage gaps"). Adding a Playwright spec that exercises the diff view + Pull flow would close the gap and prevent regressions.

## Done looks like - New e2e spec under that loads the reconciliation screen for a seeded linked event, asserts the match summary, clicks "Pull" on a differing field, and verifies the field flips to "matches" plus a new log row appears. - The Corsizio entry in lists the new testIds.

## Relevant files - (new spec) - - (testIds already present)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Fix invalid robots.txt for Lighthouse SEO

Volleyball Elite Academy development update
Volleyball Elite Academy
Fix invalid robots.txt for Lighthouse SEO

Fix invalid robots.txt for Lighthouse SEO

Volleyball Elite Academy — Development Update • May 21, 2026

Fix invalid robots.txt for Lighthouse SEO

What & Why

Lighthouse's SEO audit flags the live site's as invalid. The current file has one bad line:

Per the [sitemaps.org spec](https://www.sitemaps.org/protocol.html#submit_robots), the directive must be an absolute URL (scheme + host + path), not a relative path. That single line is what breaks the audit. As a bonus problem, there is no actually being served, so the directive points at a 404 even once the URL is made absolute.

Done looks like

  • Running Lighthouse against the deployed site no longer reports "robots.txt is not valid".
  • returns a file whose line is an absolute URL pointing at a real, reachable .
  • returns a valid XML sitemap (HTTP 200, ) listing the site's public, non-authenticated routes (home, alumni landing, events listing, store, etc. — whatever is actually public).
  • The file works the same in dev (vite) and prod (Express static serving) — no env-specific drift.

Out of scope

  • Adding meta tags, Open Graph, or per-page titles (separate SEO work).
  • Auto-generating a sitemap from the database (e.g. per-event pages). A small static list of top-level public routes is enough for this pass.
  • Submitting the sitemap to Google Search Console.

Steps

1. Make the Sitemap directive absolute — Update so the line uses the deployed absolute URL. Derive the host at request time (via the existing / helpers) by serving from an Express route instead of letting Vite serve the static file, so the same code works on and any preview deployment domain without hard-coding the host. 2. Add a minimal sitemap.xml route — Add an Express route at that returns a valid XML sitemap (matching ) listing the small set of public, crawlable URLs. Use the same base-URL helper so it matches whatever host the request came in on. Set . 3. Verify — Hit and in both dev and a fresh deploy, then re-run Lighthouse SEO and confirm the "robots.txt is not valid" warning is gone.

Relevant files

- - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Wednesday, May 20, 2026

Make the registrations CSV export honor the search box

Volleyball Elite Academy development update
Volleyball Elite Academy
Make the registrations CSV export honor the search box

Make the registrations CSV export honor the search box

Volleyball Elite Academy — Development Update • May 20, 2026

--- title: Make the registrations CSV export honor the search box ---

Make the registrations CSV export honor the search box

What & Why

Task #1057 moved the Registrations search to the server so a single query searches across every page. The CSV export was explicitly left out of scope, so right now an admin who searches "alexis rein" and clicks "Export CSV" still gets either the unfiltered list or just the on-screen page — not the matching set. This task closes that gap so what admins see is what they export.

Done looks like

  • The CSV export endpoint accepts the same query param as the list endpoint and applies the same join-against-academy_people filter.
  • The Export button on the Registrations tab forwards the active debounced search term (plus the existing status / payment / event / event-type filters) to the export.
  • Exporting with a search term returns only the matching rows; exporting with no search returns all rows.

Relevant files

  • (CSV export route — search params)
  • ( already supports — reuse it)
  • (Export button)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Clean up Launch Readiness confusion

Volleyball Elite Academy development update
Volleyball Elite Academy
Clean up Launch Readiness confusion

Clean up Launch Readiness confusion

Volleyball Elite Academy — Development Update • May 20, 2026

Clean up Launch Readiness confusion

What & Why

The sidebar entry labelled "Launch Readiness" in the SuperAdmin nav is misleading. It points to , which renders the member onboarding checklist (Welcome to Elite Volleyball Academy — complete your profile, meet Penny, register for events, etc.) — content aimed at athletes and parents, not at admins setting up the academy. SuperAdmins clicking it land on either the member checklist or a confusing "Please complete your registration first" prompt.

Separately, there is a completely orphaned page (a pre-game readiness checker for league coordinators) and a backing API endpoint that nothing in the app routes to or calls. This is dead code that should be removed so the codebase stops carrying two unrelated "launch readiness" concepts.

Done looks like

  • The misleading "Launch Readiness" sidebar entry no longer appears in the SuperAdmin nav (or is renamed so its label honestly reflects the page it links to).
  • The orphaned league-readiness page, its API endpoint, and its now-unused shared types are removed.
  • No remaining UI link reaches the deleted page; no remaining server code references the deleted endpoint or types.
  • The actual page (member onboarding) continues to work exactly as it does today for athletes and parents who reach it from the dashboard banner — that flow is not changed.

Out of scope

  • Any change to the member-facing Getting Started onboarding checklist itself ( and its endpoints) — that page stays as-is.
  • Building a real admin-facing "launch readiness" or academy-setup dashboard. If one is wanted later, it should be planned as a separate task.
  • Changes to the Coach Getting Started page.

Steps

1. Remove the misleading sidebar entry — Delete the nav-registry row that links "Launch Readiness" to for SuperAdmins. Confirm nothing else in the sidebar, tests, or System Overview registry depends on that .

2. Delete the orphaned league readiness page — Remove the page file. Confirm there is no route registered for it in and no other import.

3. Delete the orphaned server endpoint and shared types — Remove the handler in and the / interfaces in . Make sure no other server or client code still imports those types.

4. Sanity check for stragglers — Search the repo for , , and "Launch Readiness" and clean up any leftover comments, test IDs, or doc references. Update the stale comment in that refers to "the Launch Readiness checklist" so it talks about the Getting Started checklist instead.

5. Verify — Load the SuperAdmin sidebar and confirm the "Launch Readiness" item is gone. Load directly as an athlete/parent and confirm the onboarding checklist still renders normally. Run typecheck.

Relevant files

- - - - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Add Alumni Role and Complete All Role Lists

Volleyball Elite Academy development update
Volleyball Elite Academy
Add Alumni Role and Complete All Role Lists

Add Alumni Role and Complete All Role Lists

Volleyball Elite Academy — Development Update • May 20, 2026

Add Alumni Role and Complete All Role Lists

What & Why

Registration and role selection screens don't show the full set of community roles. The Alumni role is missing entirely, and several existing roles (Coordinator, Store Manager, Bookkeeper) are absent from the community join wizard's hardcoded list. The registration wizard also surfaces Superadmin and Assistant Admin to the public, which should not be self-registerable. There is also a label bug where Coordinator shows as "Board Member" in the display info.

Done looks like

  • A new Alumni role exists in the system (auto-approved, described as "Former athlete who has completed their competitive playing journey — 18 or older")
  • The registration wizard () shows all community-facing roles including Alumni, and excludes Superadmin and Assistant Admin from the public-facing list
  • The community join wizard () shows the complete role list: Alumni, Athlete (under 18 / registered by parent), Parent/Guardian, Academy Coach, School Coach, Club Coach, Referee, Volunteer, Supporter, Coordinator, Board Member, Store Manager, and Bookkeeper
  • The Coordinator role no longer incorrectly displays the label "Board Member" — it reads "Coordinator"
  • The Athlete description on both pages clearly conveys that athletes under 18 are registered by a parent/guardian, while Alumni covers those 18+ who have completed their journey

Out of scope

  • Changing what any role is permitted to access (permissions/RBAC)
  • Changing the approval workflow for existing roles
  • Any new backend logic beyond registering the ALUMNI role in the same way as ATHLETE

Steps

1. Add ALUMNI to the schema — Add to , include it in , add a entry (label: "Alumni", description: "Former athlete who has completed their competitive volleyball journey (18+)", requiresApproval: false), and fix the entry's label from "Board Member" to "Coordinator".

2. Fix the registration wizard role list — In , filter before rendering to exclude and . Add a small clarifying note near the ATHLETE and ALUMNI options explaining the age distinction (under 18 vs 18+ completed journey).

3. Update the community join wizard role list — In , add entries to the object for: (former athlete 18+), (approval required), (approval required), and (approval required). Update the entry's description to clarify it is for athletes under 18 registered by a parent/guardian. Choose appropriate icons from for each new entry.

Relevant files

- - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Tuesday, May 19, 2026

Let admins filter the backlog-threshold history before exporting

Volleyball Elite Academy development update
Volleyball Elite Academy
Let admins filter the backlog-threshold history before exporting

Let admins filter the backlog-threshold history before exporting

Volleyball Elite Academy — Development Update • May 19, 2026

--- title: Let admins filter the backlog-threshold history before exporting ---

Let admins filter the backlog-threshold history before exporting

## What & Why The new CSV export at always returns the full history (capped at 10,000 rows). The coach-search-alert CSV export accepts , , , and filters that mirror the on-screen list, so admins can scope archived records. The backlog history will gain similar usefulness if admins can narrow by editor and date range before downloading.

## Done looks like - The CSV endpoint accepts optional , , and query params and applies them to the same query. - The "Recent changes" UI grows matching filter controls and forwards them to both the JSON history fetch and the Export CSV link.

## Relevant files - (history + history.csv endpoints around lines 1793 and 1844) - ( around line 1469)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Pin People as a sidebar shortcut under Control Panel

Volleyball Elite Academy development update
Volleyball Elite Academy
Pin People as a sidebar shortcut under Control Panel

Pin People as a sidebar shortcut under Control Panel

Volleyball Elite Academy — Development Update • May 19, 2026

--- title: Pin People as a sidebar shortcut under Control Panel ---

Pin People as a sidebar shortcut under Control Panel

What & Why

The admin People directory () is the most-used SuperAdmin tool but lives two clicks deep at — admins have to click "Control Panel" in the sidebar and then click the "People" tab. This task pins People as a direct sidebar shortcut nested under Control Panel so it's reachable in a single click.

Done looks like

  • The admin sidebar shows a "People" entry nested directly under "Control Panel" (with the same iconography conventions as other nested entries).
  • Clicking it navigates to with the Directory tab already active.
  • The active-state highlight in the sidebar correctly highlights "People" when on and falls back to highlighting "Control Panel" on every other tab.
  • The shortcut is visible only to roles that can already access the Control Panel (SuperAdmin, Coordinator) — never to parents/athletes/coaches.
  • Mobile and desktop sidebar both expose the shortcut.

Out of scope

  • Adding shortcuts for other Control Panel tabs (Events, Registrations, Staffing, Tools, etc.) — this task is People only.
  • Restructuring the Control Panel page itself.
  • Renaming "People" or changing its sub-views.

Steps

1. Register the shortcut — Add a nested nav entry under the existing registry key for People with the right URL and role gating. 2. Render nested children in the sidebar — Update the sidebar component to render nested children for (if it doesn't already) and apply the active-state rule above. 3. Smoke-test the navigation — Manually verify each role sees/doesn't see the shortcut and that clicking it lands on the right tab with the right sub-view.

Relevant files

- - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Show the same friendlier duplicate-email error when admins create a new person

Volleyball Elite Academy development update
Volleyball Elite Academy
Show the same friendlier duplicate-email error when admins create a new person

Show the same friendlier duplicate-email error when admins create a new person

Volleyball Elite Academy — Development Update • May 19, 2026

--- title: Show the same friendlier duplicate-email error when admins create a new person ---

Show the same friendlier duplicate-email error when admins create a new person

## What & Why Task #1045 upgraded the duplicate-email guard on PATCH to return a 409 with . The sibling guard on POST ( ~line 561) still returns a flat 400 with the generic "A person with this email already exists" message. Bulk imports and the "Add person" flow hit the POST route, so admins get the worse error there.

## Done looks like - POST returns the same shape as PATCH (409 + structured ) when an email is already taken - The "Add person" UI surfaces the friendly "Email already on file for " message without a second lookup hop - A route-level test in covers the new POST response shape

## Relevant files - (POST ~line 561) - (mirror as a POST companion test)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Catch typos in rental booking status before they reach the database

Volleyball Elite Academy development update
Volleyball Elite Academy
Catch typos in rental booking status before they reach the database

Catch typos in rental booking status before they reach the database

Volleyball Elite Academy — Development Update • May 19, 2026

--- title: Catch typos in rental booking status before they reach the database ---

Catch typos in rental booking status before they reach the database

## What & Why Task #1043 tightened the rental booking schema so the status field must be one of "BOOKED", "CONFIRMED", or "CANCELLED" (previously any text was accepted). The PATCH route is now covered by tests, but the create-and-bulk-import routes (POST and POST ) share the same schema and have no test that locks in this behavior. Without a test, a future change could silently weaken the create path's status validation and let bad values back into the database.

## Done looks like - A test confirms POST rejects an invalid status (e.g. "confirmed" lowercase, or "PENDING") with 400 - A test confirms POST rejects the batch when any row carries an invalid status - A test confirms valid uppercase enum values still pass through and reach storage

## Relevant files - (POST around L2913, POST around L2931) - (existing scaffold and mocks; add a new describe block alongside the Task #1043 lockdown tests) - (insertGymRentalSchema, GYM_RENTAL_STATUS)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Let admins jump to the conversation when a stale digest link can't find its draft

Volleyball Elite Academy development update
Volleyball Elite Academy
Let admins jump to the conversation when a stale digest link can't find its draft

Let admins jump to the conversation when a stale digest link can't find its draft

Volleyball Elite Academy — Development Update • May 19, 2026

--- title: Let admins jump to the conversation when a stale digest link can't find its draft ---

Let admins jump to the conversation when a stale digest link can't find its draft

## What & Why Task #1038 added an inline notice in the Recent AI drafts panel telling admins "We couldn't find the draft from your digest (#N) — it may have rolled off your recent list." That tells the admin WHY nothing auto-expanded, but it leaves them at a dead end: the digest was trying to surface a specific conversation, and they now have no fast way to get to it.

The daily digest already knows the thread id behind every pushback row ( in builds the draft URL from a row that includes ). If we extend the deep-link shape to carry the thread id alongside the draft id (e.g. ), the dead-letter branch can render an "Open the conversation anyway" action inside the notice that hands off to the existing Conversations tab thread-loader (the same one in calls).

## Done looks like - includes the thread id in the deep-link URL. - When the dead-letter notice appears, it shows a button that opens the matching conversation thread. - The happy-path deep-link still works unchanged when the draft IS in the recent window.

## Relevant files - () - ( deep-link effect + new notice block) - (URL-shape coverage that will need updating) - (notice coverage to extend)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Monday, May 18, 2026

Cover the in-cooldown push nudge hint on guardian rows

Volleyball Elite Academy development update
Volleyball Elite Academy
Cover the in-cooldown push nudge hint on guardian rows

Cover the in-cooldown push nudge hint on guardian rows

Volleyball Elite Academy — Development Update • May 18, 2026

--- title: Cover the in-cooldown push nudge hint on guardian rows too ---

Cover the in-cooldown push nudge hint on guardian rows

## What & Why The SOF admin read-rate popover renders the same amber "Cooling down — nudged at

## Done looks like - A browser test seeds an in-cooldown (sent_at within the last 6h) row keyed off a guardian's person_id, with a resolvable . - Asserts reads "Cooling down — nudged at

## Relevant files - ( cooldown attach) - ( audience="guardian") - (email-branch spec to mirror) - (athlete push fixture pattern)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Cover the assignee dropdown URL sync with a browser test

Volleyball Elite Academy development update
Volleyball Elite Academy
Cover the assignee dropdown URL sync with a browser test

Cover the assignee dropdown URL sync with a browser test

Volleyball Elite Academy — Development Update • May 18, 2026

--- title: Cover the assignee dropdown URL sync with a browser test ---

Cover the assignee dropdown URL sync with a browser test

## What & Why Task #1030 added Playwright coverage for status / unread / search filter URL sync in the Conversations inbox, plus a deep-link test that proves survives the parent's strip-on-mount. What is still uncovered in a real browser is the user-driven path: opening the assignee dropdown and picking a value (Mine / Unassigned / a specific admin) must mirror the choice into the URL via pushState, and Browser Back must restore the prior assignee selection. A regression that breaks just the assignee branch of the URL sync would slip past the new spec because none of its three tests click the dropdown.

## Done looks like - A Playwright spec opens the assignee dropdown, picks "Mine", and asserts the URL gains and the chip label updates. - The spec switches to "Unassigned" and asserts the URL flips to . - Browser Back restores the prior view (URL + chip text), proving the popstate handler reads the assignee branch correctly.

## Relevant files - ( mapping + popstate handler) - (sibling pattern + auth fixture)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Fix Command Center Frozen Page

Volleyball Elite Academy development update
Volleyball Elite Academy
Fix Command Center Frozen Page

Fix Command Center Frozen Page

Volleyball Elite Academy — Development Update • May 18, 2026

Fix Command Center Frozen Page

What & Why

On (and likely other routes), the entire page becomes unclickable: and are stuck on , the Google Translate selector and every link/button stop responding, and the Issues panel reports 22 JavaScript errors on plus accessibility violations on the registrations forms. The root cause is the mobile sidebar Sheet (Radix Dialog) entering an "open" state and never running its close handler — most likely because one of those 22 errors throws mid-render and prevents Radix from removing its scroll/pointer lock. We need to stop the page from getting wedged and clean up the underlying errors and a11y issues so it does not happen again.

Done looks like

  • Loading on desktop and mobile leaves the page fully interactive: dropdowns, sidebar links, the Google Translate selector, and tab switches all respond.
  • no longer has or lingering after navigating into or out of the Registrations tab.
  • The 22 console errors on Command Center are gone (or reduced to known, harmless warnings) and any remaining error inside the mobile sidebar or a dialog cannot leave the body in a locked state.
  • The Issues panel shows zero "form field should have an id or name attribute" and zero "No label associated with a form field" violations on the Registrations tab.
  • Verified on the deployed site at elitevolleyball.training, not just locally.

Out of scope

  • Redesigning the Command Center layout or the mobile navigation.
  • Replacing Radix UI or shadcn Sheet/Dialog primitives.
  • Fixing unrelated accessibility issues on pages other than Command Center → Registrations.
  • Changes to the Google Translate widget itself (only verify it becomes clickable again).

Steps

1. Reproduce and capture the failure. Load in dev and on the deployed site, confirm lands on , and collect the 22 console errors from the Application → Issues panel so we know exactly which throw is triggering the locked state. 2. Fix the root-cause errors on the Registrations tab. Walk through each error from step 1 and fix it at the source (bad data access, missing query result handling, undefined props, etc.). Most or all of the 22 errors should be eliminated. 3. Harden the mobile sidebar Sheet against future lockups. Make sure the Sheet that wraps the mobile sidebar cannot leave in a / state if a child throws — e.g. an error boundary around the Sheet content that forces on error, plus a route-change effect that closes the mobile sidebar whenever the location changes. The fix must work for any page that uses the mobile sidebar, not just Registrations. 4. Clean up the form accessibility violations on Registrations. Add proper and attributes to the 2 flagged form fields and associate each of the 7 flagged labels with their inputs (either by nesting the input inside the label or by matching to the input's ). Use the existing shadcn / / pattern where possible so this stays consistent with the rest of the app. 5. Verify end-to-end. After deploying, reopen on desktop and mobile widths, confirm the page stays interactive, the Issues panel is clean, and the Google Translate selector works again.

Relevant files

- - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Fix Command Center Frozen Page

Volleyball Elite Academy development update
Volleyball Elite Academy
Fix Command Center Frozen Page

Fix Command Center Frozen Page

Volleyball Elite Academy — Development Update • May 18, 2026

Fix Command Center Frozen Page

What & Why

On (and likely other routes), the entire page becomes unclickable: and are stuck on , the Google Translate selector and every link/button stop responding, and the Issues panel reports 22 JavaScript errors on plus accessibility violations on the registrations forms. The root cause is the mobile sidebar Sheet (Radix Dialog) entering an "open" state and never running its close handler — most likely because one of those 22 errors throws mid-render and prevents Radix from removing its scroll/pointer lock. We need to stop the page from getting wedged and clean up the underlying errors and a11y issues so it does not happen again.

Done looks like

  • Loading on desktop and mobile leaves the page fully interactive: dropdowns, sidebar links, the Google Translate selector, and tab switches all respond.
  • no longer has or lingering after navigating into or out of the Registrations tab.
  • The 22 console errors on Command Center are gone (or reduced to known, harmless warnings) and any remaining error inside the mobile sidebar or a dialog cannot leave the body in a locked state.
  • The Issues panel shows zero "form field should have an id or name attribute" and zero "No label associated with a form field" violations on the Registrations tab.
  • Verified on the deployed site at elitevolleyball.training, not just locally.

Out of scope

  • Redesigning the Command Center layout or the mobile navigation.
  • Replacing Radix UI or shadcn Sheet/Dialog primitives.
  • Fixing unrelated accessibility issues on pages other than Command Center → Registrations.
  • Changes to the Google Translate widget itself (only verify it becomes clickable again).

Steps

1. Reproduce and capture the failure. Load in dev and on the deployed site, confirm lands on , and collect the 22 console errors from the Application → Issues panel so we know exactly which throw is triggering the locked state. 2. Fix the root-cause errors on the Registrations tab. Walk through each error from step 1 and fix it at the source (bad data access, missing query result handling, undefined props, etc.). Most or all of the 22 errors should be eliminated. 3. Harden the mobile sidebar Sheet against future lockups. Make sure the Sheet that wraps the mobile sidebar cannot leave in a / state if a child throws — e.g. an error boundary around the Sheet content that forces on error, plus a route-change effect that closes the mobile sidebar whenever the location changes. The fix must work for any page that uses the mobile sidebar, not just Registrations. 4. Clean up the form accessibility violations on Registrations. Add proper and attributes to the 2 flagged form fields and associate each of the 7 flagged labels with their inputs (either by nesting the input inside the label or by matching to the input's ). Use the existing shadcn / / pattern where possible so this stays consistent with the rest of the app. 5. Verify end-to-end. After deploying, reopen on desktop and mobile widths, confirm the page stays interactive, the Issues panel is clean, and the Google Translate selector works again.

Relevant files

- - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Lock down rental edits so only admins can change facility bookings

Volleyball Elite Academy development update
Volleyball Elite Academy
Lock down rental edits so only admins can change facility bookings

Lock down rental edits so only admins can change facility bookings

Volleyball Elite Academy — Development Update • May 18, 2026

--- title: Lock down rental edits so only admins can change facility bookings ---

Lock down rental edits so only admins can change facility bookings

## What & Why The endpoints for editing and deleting gym rentals (, ) currently accept any logged-in user — they only check and skip the SuperAdmin check used elsewhere. That means any signed-in account (including parents and athletes) could change a rental's times, court count, or status and silently break the scheduling capacity for real events. Now that courtCount drives capacity math, the blast radius of unauthorized edits is bigger.

## Done looks like - PATCH and DELETE on require SuperAdmin (matching ) - PATCH validates the full body with a Zod partial schema (start/end time format, status enum, courtCount ≥ 1, etc.) instead of forwarding raw - Route tests cover the unauthorized-user 403 case and the invalid-body 400 case

## Relevant files - (, ) - (insertGymRentalSchema — extend for an updateGymRentalSchema) -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Parent & Athlete Landing Pages

Volleyball Elite Academy development update
Volleyball Elite Academy
Parent & Athlete Landing Pages

Parent & Athlete Landing Pages

Volleyball Elite Academy — Development Update • May 18, 2026

Parent & Athlete Landing Pages

What & Why

Parents are the paying customers — they register athletes, pay fees, and need a single, calm starting surface that guides them to the value of the academy. Today, when a parent logs in they land on the generic (LandingPage) or , which is shared with every other persona and surfaces everything at once. We will introduce a dedicated, opinionated post-login surface for the Parent persona, plus a tightly linked Athlete surface that is fully visible to the connected parents (athletes are under 18, so parents must see everything the athlete sees).

Both surfaces will share the same navigation spine but be visually distinguished by a thin top banner / background-tint cue so a parent always knows which "room" they are in. Event information — past, present, and future — is the headline pillar; evaluations, communications, payments, and athlete development sit alongside it.

Done looks like

  • A parent who logs in is sent to (a new Parent Home), not the generic dashboard.
  • The Parent Home shows, above the fold:
- A thin parent-tinted top banner (e.g. warm/family color from the design tokens) labelled "Parent Home" with the parent's name and a child switcher. - My Athletes — one row per child with name, photo, current training team(s) / status, and a "View athlete" button that opens the Athlete surface. - Events at a glance — three clearly separated columns/cards: Upcoming, Today / This week, and Recent / Past (last 30 days), each filtered to the parent's children. Each row links to the event detail and shows registration + payment state. - Action needed strip — outstanding payments, unsigned waivers, pending registrations, training-team responses due, unread conversations.
  • A "pillar" navigation row below the hero, in this fixed order, each opening a dedicated section/page:
1. Events (past / present / future, with registration history) 2. Payments & Registrations 3. Communications (announcements + inbox conversations) 4. Athlete Development (evaluations, feedback, training-team status, reports) 5. Connections (coaches, co-guardians, family directory) 6. Settings (notifications, profile, family management)
  • Visiting opens the Athlete Home for one child, scoped to that athlete. It carries an athlete-tinted banner (different color from the parent banner — e.g. cool/court color) labelled "Athlete: ". The page shows:
- Upcoming schedule (next 14 days), recent attendance, latest evaluation/feedback, current training team(s), recent messages mentioning the athlete, and a "switch to another athlete" pill row. - A persistent "Viewing as parent" chip in the banner when accessed by a parent, with a "back to Parent Home" link.
  • The same Athlete Home is reachable by the athlete themselves at (their own) with identical content but the athlete-tinted banner only (no "Viewing as parent" chip). Athletes cannot see other athletes' homes; parents can only see their own connected children's homes (enforced server-side).
  • Post-login routing: if the logged-in user has the PARENT academy role and no higher-precedence superadmin/coordinator persona is explicitly selected, the post-login redirect lands on . If the user is an athlete-only login, they land on . SuperAdmins / coordinators continue to land where they do today.
  • The Parent and Athlete surfaces are clearly two different rooms at a glance — same nav spine, same shadcn components, but distinguishable banner color/tint plus surface label. Both meet AA contrast in light and dark mode.
  • Every interactive and meaningful element on both pages has a stable following the fullstack-js convention.
  • A short e2e spec (or two) confirms: parent login → lands on ; parent clicks an athlete → lands on ; server rejects a parent trying to view an unconnected athlete's home.

Out of scope

  • Building any new event, evaluation, payment, or conversation back-end logic — these surfaces compose existing APIs (, , , , , , , , , , etc.) — no schema changes.
  • Removing or deleting the current or page. They stay as-is for other personas and as a deep-link target; the new Parent Home becomes the default entry for parents and links into the existing pages where useful. A follow-up task can decide whether to retire once the new surface stabilizes.
  • Coach / Referee / Coordinator / SuperAdmin landing redesigns.
  • Mobile-app (Expo) versions — web only for this task.
  • New notification or email flows.
  • Any change to RBAC rules; we reuse the existing parent-can-see-own-children authorization that powers and .

Steps

1. Role-aware post-login routing — Update the existing post-login redirect so a logged-in PARENT lands on and an athlete-only login lands on , preserving any explicit override and the existing SuperAdmin/Coordinator behaviour. Add checks so a user who is both parent and coach keeps their current selectable persona, but defaults to parent on first login of the day. 2. Surface chrome (banner + tint tokens) — Add two named surface tints to the existing Tailwind/theme tokens (one "parent" and one "athlete"), define them for light and dark mode at AA contrast, and build a small component used by both new pages. The banner is a thin (~6–8px) top stripe plus a subtle full-page background tint so the room is unmistakable without being loud. 3. Parent Home page () — New page component that composes existing TanStack queries (, , , , , , family event summaries) to render the hero (My Athletes + child switcher), the three Events columns (past / present / future scoped to the family), the Action Needed strip, and the six-pillar nav row. Reuse existing cards from where they already exist (e.g. , payment/registration cards) rather than recreating them — extract them into shared components if needed. 4. Athlete Home page ( and ) — Single page component that takes a from the route and composes existing queries (, , , , child schedule from or per-athlete endpoint) into: schedule (next 14 days), attendance summary, latest evaluation / feedback, training team status, recent communications mentioning the athlete, and a child-switcher pill row when the viewer is a parent. Render the athlete-tinted and a "Viewing as parent — back to Parent Home" chip when the viewer is a parent and not the athlete themselves. 5. Pillar sub-routes & wiring — Wire the six pillar buttons on Parent Home to existing routes (Events → filtered to family; Payments → existing payment portal; Communications → conversations inbox; Athlete Development → development report; Connections → community directory + co-guardians; Settings → notification settings / family management). Where an existing page does not have a family-scoped view, add a thin "filter by my family" toggle rather than building new pages. 6. Route registration & guards — Register , , and in , each wrapped in a that requires parent (for ) or athlete-or-parent-of (for ). Server-side: ensure the existing parent-can-see-child authorization already enforced by and is the gate (no new auth code unless a gap is found — if a gap is found, fix it in this task and note it). 7. SEO + accessibility polish — Per-page titles ("Parent Home — Elite Volleyball" and "Athlete: — Elite Volleyball"), meta descriptions, focus order through hero → events → pillars, and dark-mode parity for both tints. Verify with keyboard nav and a quick contrast check. 8. Tests — One e2e spec for the parent path (login → → click athlete → → see schedule + latest evaluation), one spec for athlete self-login landing on their own athlete home, and a backend test confirming rejects a parent requesting an unconnected athlete. Reuse the existing playwright + vitest patterns in and . 9. Update — Add the two new routes and the parent/athlete surface convention (banner tints, default post-login destinations) under "Where things live" + "Architecture decisions" so the convention is discoverable.

Architectural constraints

  • Compose, do not duplicate — every data point already exists in an API; this task is an IA + chrome reshuffle, not new domain logic.
  • Parent visibility is total — anything an athlete can see in their own Athlete Home, the connected parent must also see in . The two routes render the same component with a prop that only changes the banner chip, not the data.
  • Athlete privacy is bounded — athletes can never see another athlete's home; parents can only see their own connected children. Enforce server-side, not just in the UI.
  • Surface cue is visual, not modal — never block the parent inside a "parent mode" they must dismiss; the tint and banner are passive cues.
  • Terminology — Athletes (not Players), Parents/Guardians, Days (not Sessions), per the existing standards in .
  • Timezone — All dates parsed as America/Regina local time, per existing convention.

Relevant files

- - - - - - - - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Sunday, May 17, 2026

EV ↔ Corsizio side-by-side reconciliation screen

Volleyball Elite Academy development update
Volleyball Elite Academy
EV ↔ Corsizio side-by-side reconciliation screen

EV ↔ Corsizio side-by-side reconciliation screen

Volleyball Elite Academy — Development Update • May 17, 2026

EV ↔ Corsizio side-by-side reconciliation screen

What & Why

Once a Corsizio event is linked to an EV academy event, the two sides drift over time — Corsizio is the source of truth for registrations and payments, but EV holds checklists, attendance, communications, and reports. Today an admin can see both sides only by opening the Corsizio Sync dashboard for one and the EV event editor for the other. This task adds a single side-by-side reconciliation view per linked event pair, showing every metadata field on both sides with a diff highlight and a per-field "Pull from Corsizio" / "Keep EV value" choice. Nothing auto-applies — admin chooses every field individually.

Done looks like

  • From the Corsizio Sync dashboard, every linked event row has a "Reconcile" button that opens a new side-by-side screen (or modal) for that pair.
  • The screen shows a two-column layout: EV value on the left, Corsizio value on the right, with one row per comparable field (name, start date, end date, location, max spots, price, registration count, status).
  • Fields that match render in a neutral tone; fields that differ render with an amber highlight and a "≠" indicator.
  • Each differing field has two buttons: "Pull from Corsizio" (overwrites the EV value) and "Keep EV value" (dismisses the diff for this field for the current session). Buttons are disabled for fields that already match.
  • Pulling from Corsizio writes to the EV academy_events row and triggers a re-render of that field as "matched". The change is logged so admins can audit who reconciled what.
  • A header summary shows "X of Y fields match" and a "Last reconciled" timestamp.
  • The screen never writes back to Corsizio (the API is read-only — confirmed by the 2026-05-13 audit in §3).

Out of scope

  • Reconciling individual registrations (this task is event metadata only).
  • Bulk "pull all" buttons — every field decision is per-field by design.
  • Auto-running reconciliation on a schedule (admins open it manually).
  • Pushing any value back to Corsizio.

Steps

1. Reconcile route + entry point — Add a new admin-only page at (or equivalent), and add the "Reconcile" button to each linked-event row in the existing Corsizio Sync dashboard. 2. Diff data fetch — Build a server endpoint that, given a linked Corsizio event ID, returns both sides as a structured payload: the latest cached Corsizio metadata (already in ) and the current EV academy_events row, with a per-field comparison. 3. Side-by-side UI — Render the two columns with one row per comparable field, amber highlight on diffs, neutral tone on matches, and the two per-field action buttons. 4. Pull-from-Corsizio mutation — Implement the per-field write to academy_events (one field at a time, each in its own mutation). Refresh the diff after each successful write. 5. Audit logging — Record each "Pull from Corsizio" action (who, when, which field, old → new) in an existing audit log or a small new table so admins can later see what was changed. 6. Header summary — Show "X of Y fields match" plus the most recent timestamp at the top of the screen.

Relevant files

- - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Refresh the Manage Team dialog automatically after Select / Grant Override

Volleyball Elite Academy development update
Volleyball Elite Academy
Refresh the Manage Team dialog automatically after Select / Grant Override

Refresh the Manage Team dialog automatically after Select / Grant Override

Volleyball Elite Academy — Development Update • May 17, 2026

--- title: Refresh the Manage Team dialog automatically after Select / Grant Override ---

Refresh the Manage Team dialog automatically after Select / Grant Override

## What & Why The Team Detail dialog on /command-center?tab=training-teams renders from a useState snapshot of the team object captured at click time. After a Select, Grant Override, Waitlist, or Not Selected action, the underlying query refetches, but the open dialog keeps showing the pre-mutation roster (the moved athlete still appears in the Pending list, the Selected table doesn't update, etc.). Admins have to close the dialog and re-open it to see the result of the action they just took — which is the exact workaround the new e2e spec for task #1015 had to apply.

## Done looks like - After any status mutation in the Manage Team dialog, the Pending / Selected / Waitlisted / Not Selected sections update in place without needing a close + re-open - The selection counts at the top of the dialog (Selected / Pending / Waitlisted / Spots Left) reflect the latest server state - The e2e spec at can drop its close+reopen workaround and assert the move directly inside the dialog

## Relevant files - ( useState + props) - (workaround to remove)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

Show the same 'delivery status pending' caveat in alumni and other lightweight communication summaries

Volleyball Elite Academy development update
Volleyball Elite Academy
Show the same 'delivery status pending' caveat in alumni and other lightweight communication summaries

Show the same 'delivery status pending' caveat in alumni and other lightweight communication summaries

Volleyball Elite Academy — Development Update • May 17, 2026

--- title: Show the same 'delivery status pending' caveat in alumni and other lightweight communication summaries ---

Show the same 'delivery status pending' caveat in alumni and other lightweight communication summaries

## What & Why Task #1013 wired the silent-webhook caveat into the family dashboard's RecentCommunicationsCard and MyRecentCommunicationsCard, mirroring what Task #997 added to PersonCommunicationTimeline. Other lightweight summary surfaces that list outbound emails (e.g. alumni profile communication summaries, coach quick-look cards, admin person mini-cards) still render only the title and timestamp without surfacing the stale-status caveat. Users on those surfaces will still see a confirmed-looking row when the Resend webhook is silent.

## Done looks like - Audit lightweight email-summary widgets outside PersonCommunicationTimeline (e.g. alumni profile cards, coach mini-summaries) and wire each one to - Each widget renders an inline note + per-row "Pending" badge consistent with RecentCommunicationsCard / MyRecentCommunicationsCard. - A small browser test pins the new behavior on at least one of the new surfaces.

## Relevant files - (StaleStatusNote / StaleStatusRowBadge — extract as shared if reused) - Any other component rendering recent emails for a person (search for and similar test-id patterns to find candidates)

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training

On-the-hour Day times fit off-the-hour rentals

Volleyball Elite Academy development update
Volleyball Elite Academy
On-the-hour Day times fit off-the-hour rentals

On-the-hour Day times fit off-the-hour rentals

Volleyball Elite Academy — Development Update • May 17, 2026

On-the-hour Day times fit off-the-hour rentals

What & Why

When a gym facility books a rental window that doesn't land on a clean clock hour (e.g. Beach-Full 5:00 PM – 6:55 PM, 115 min), an admin scheduling that Day for athletes naturally wants to publish a clean on-the-hour time like 5:00 PM – 7:00 PM (120 min) so athletes aren't confused by odd minute boundaries. Today this trips the rental-capacity validator on the events-management page with:

> Error: Not enough time on rental Beach-Full 5:00 PM-6:55 PM. 60 min available for this event, but this date needs 115 min.

The validator compares Day minutes against the rental's exact minute window. We want admin-friendly rounding so a Day whose window is on the hour fits a rental window that is almost the same (off by a few minutes on the boundary) without erroring. Facility booking storage stays accurate; only the validator's interpretation gets a small slack.

The user request, verbatim: pick on-the-hour Day times that bracket the rental window (e.g. 5:00–7:00 PM for a 5:00–6:55 PM rental) without an error toast, because the odd minute boundaries are confusing to athletes.

Done looks like

  • An admin on who edits a Day to a clean on-the-hour Start/End (e.g. 5:00 PM – 7:00 PM) that brackets a linked off-the-hour rental window (e.g. 5:00 PM – 6:55 PM) can save the event without the "Not enough time on rental" error, as long as the Day's start is at or after the rental's start (allowing the same small slack) and the Day's end is no more than 15 minutes past the rental's end.
  • The same tolerance applies in both the bulk preflight () and the per-date assignment () so the toast and the actual save never disagree.
  • Auto-fill behaviour when linking a rental on the Day editor populates the Day with on-the-hour rounded Start/End times (round start down, round end up to the nearest quarter-hour, capped at the closest clean :00) so admins don't have to fix it manually. The actual rental record's times are not modified.
  • A small helper hint near the Start/End inputs explains the new behaviour: "Your Day can run up to 15 min past the rental's posted end time."
  • Capacity math itself (preventing two events from sharing the same rental window) is unchanged for other events linked to the same rental — slack is granted only against the bracketing rental window for the Day being edited, not added to the rental's bookable minute pool against other events.
  • Existing per-court overlap detection (Task #1042) is unchanged.

Out of scope

  • Changing how / are stored or displayed in the rentals list — the facility's booking-of-record is preserved.
  • Two-way sync to push rounded times back to Corsizio or the facility.
  • A configurable tolerance UI; 15 minutes is hardcoded with a single shared constant.
  • Changing the half-open same-court overlap rule (touching at the boundary still fine).
  • The Day 2 / Day 3 both linked to the same single-day rental scenario shown in the screenshot — that "230 min proposed vs 115 min capacity" condition is the rental being legitimately overbooked across two calendar days against a single-date rental, and admins should still see an error there.

Steps

1. Shared tolerance constant + capacity math — Add a constant. In the capacity helper, when computing whether the proposed Day(s) for the excluded event fit, treat the rental window as effectively extended by the tolerance on the end (and same allowance on the start, never below 0). Apply this only to the proposed (excluded-event) minutes comparison, not to the cross-event pool. Update the helper's return shape to expose the effective window so callers can show consistent messaging. 2. Preflight + assign routes use the tolerant math — Both and already call the shared helper; verify the error messages now reflect the tolerant window (or simply no longer fire when the Day is within tolerance) and adjust phrasing so the message references the posted rental window plus the slack ("up to 7:00 PM with a 15-min grace"). 3. Auto-fill rounds to the hour — When an admin picks a rental in the Day editor and the form auto-fills Start/End from the rental window, round the Day's Start down and End up to the nearest quarter-hour, preferring the nearest clean :00 when within tolerance, so a 5:00 PM – 6:55 PM rental auto-fills as 5:00 PM – 7:00 PM. Skip rounding when the rental window is already on a clean hour boundary. 4. Inline UI hint — Add a single short helper line under the Day's Start/End inputs (or under the rental-link row) explaining the tolerance so admins understand why an apparently "over" time is allowed. Use the shared constant for the number. 5. Tests — Extend with cases for: (a) on-the-hour Day fits an off-the-hour rental within tolerance (no error), (b) Day exceeds tolerance (still errors), (c) the cross-event minute pool isn't enlarged by tolerance (two events together still detect a real overbook). Add a quick unit test for the auto-fill rounding helper.

Constraints

  • America/Regina local-time parsing stays the way it is — all of this is minute-math, not date-math. Don't introduce UTC.
  • Terminology in user-facing strings: "Days", "athletes" (per ).
  • Do not modify schema or storage.

Relevant files

- - - - - - - - -

Volleyball Elite Academy

Reply to this email — we read every reply.

You received this because you have an account with Volleyball Elite Academy.

elitevolleyball.training