Consolidated admin-dashboard fix train (supersedes #736–#739, includes #734) - #740
Conversation
Auto-generated by .github/workflows/docs-regen.yml. Sources: src/lib/types/database.ts, src/app/**/page.tsx, src/app/**/actions/**/*.ts, src/hooks/**/*.ts.
…ting branches trigger billed preview builds Old dependabot branches carry a vercel.json from before git.deploymentEnabled landed, and Vercel reads the config from the branch being built — so every auto-rebase push fired a preview build (see the failed tailwindcss-4.3.1 build). Rebases now happen only on demand (@dependabot rebase). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
…chore/train2-20260703
…allback with retry [vercel skip]
P0 from the 2026-07-03 Mission Control sweep. Root cause (H1 confirmed):
every get_admin_*_rollup SECURITY-DEFINER function gates on
users.role='admin', but baseball onboarding's users upsert
({ onConflict: 'id' }, service-role client, no ignoreDuplicates) clobbered
the allowlisted super-admin's row down to role='coach' — so
requireSuperAdmin() passed and every Bridge panel then died with 42501.
The role was restored live (2026-07-03 04:18Z, verified: simulated
authenticated calls to get_admin_dashboard_rollup/get_admin_rounds_rollup
return data); this commit makes the demotion impossible to reintroduce:
- ensureUserRowPreservingAdmin(): both baseball onboarding users-row writes
now insert-if-missing / allow player<->coach conversion / NEVER overwrite
an existing 'admin'. (Golf onboarding already used ignoreDuplicates and
could not demote.)
- PanelBoundary/PanelStale: the error fallback claimed "showing last known
data" while rendering none. Copy now tells the truth ("temporarily
unavailable") and ships a real retry (router.refresh() + boundary remount).
- panel-boundary tests: assert the honest copy, the retry path, and that the
misleading claim never comes back.
H2 (session not reaching the RPC) ruled out: fetchAdminRollupA uses the
request-scoped server client and the RPC succeeds with only a sub claim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
… event_type [vercel skip] P2 from the 2026-07-03 Mission Control sweep (Postgres logs still firing 400s at 04:03 UTC). Same committed-not-applied class as wave 1 (20260702095900): 20260624000090_baseball_settings_os.sql used CREATE TABLE IF NOT EXISTS, which silently no-op'd for baseball_program_settings and baseball_integration_configs (both pre-existed under older schemas), so the whole intended settings-OS column set never landed. - baseball_program_settings: full settings-OS parity (36 columns incl. brand_accent + appearance_theme — the dominant >=85/4-min 400) + 7 guarded CHECKs - baseball_integration_configs: adapter-contract columns (provider_key, display_name, integration_level, status, config, created_by...) + the UNIQUE (team_id, provider_key) that upsertIntegration's onConflict needs + guarded DROP NOT NULL on legacy integration_key (db-migration-review required fix — the insert path never writes it) - baseball_catching_events: event_type + CHECK (the one code-referenced column wave 1 missed) Reviewed by db-migration-reviewer (golf-safety PASS, fresh-DB idempotent — guard names match the auto-generated inline-CHECK names, so a fresh DB no-ops cleanly). APPLIED TO PROD 2026-07-03 via MCP before this commit; verified: all columns present, integration_key nullable, the previously 400ing brand_accent/appearance_theme read runs clean. All three tables were 0 rows — backfill-free. The two other runtime-log 42703s (baseball_coaches_2.first_name, baseball_stat_visual_views.owner_user_id) are already code-side fixed on main (practice.ts selects full_name; stat-visual-views.ts dropped owner_user_id) — no schema change needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
…(digest 3173807121) [vercel skip]
P1 from the 2026-07-03 Mission Control sweep. Vercel prod runtime error on
/golf/dashboard + /golf/dashboard/coachhelm: "Functions cannot be passed
directly to Client Components" with payload {$$typeof, render: function,
displayName} — a lucide forwardRef component crossing the server->client
boundary.
Root cause: EmptyState is 'use client' (framer-motion) and takes
icon?: LucideIcon (a component TYPE). Four server components passed icons
that way — most damaging the golf (dashboard) group's not-found.tsx, so
EVERY notFound() under the dashboard (deleted round/player/qualifier ids)
crashed to the generic RSC error instead of the branded 404.
Fix: EmptyState's icon prop now also accepts a rendered ELEMENT
(serializable across the boundary); the chip sizes element icons via
[&>svg] classes so they match the component-type path. The four server
call sites (dashboard not-found, travel, stats/team x2) pass
icon={<Icon strokeWidth={1.75} />}. Client callers passing component types
are untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
…ention mirror [vercel skip] The 2026-07-03 incident export (93 incidents/24h) was mostly noise burying signal: CI dev servers (/home/runner/...), local dev + next start (/Users/...), and Vercel preview/prod BUILDS (/vercel/path0/...) all hold prod Supabase creds and were writing their errors into the prod admin_events/error_logs feed the Bridge reads. - shouldPersistAdminTables(): admin-table writes only when VERCEL_ENV==='production' and not during phase-production-build; ADMIN_EVENTS_FORCE_CAPTURE=1 escape hatch. Applied at every writer: captureServerTrace (logServerError/Exception/Event), logAdminEvent (admin-logger), /api/log-error, /api/admin/log-event. Off-prod keeps console + Sentry visibility. - isNextControlFlowError(): DYNAMIC_SERVER_USAGE / NEXT_* digests are framework signals, not incidents — skip recording (130 phantom "Dynamic server usage" events, one burst per preview build). - /golf/admin/demo-sessions: force-dynamic — admin data is per-request (cookies), so prerender attempts stop throwing inside getDemoSessions. - 20260703043000_admin_events_retention_pg_cron.sql: in-repo mirror of the retention job applied live 2026-07-03 (nightly purge >180d; admin_events was 91k rows/287MB with no policy). Verified live: cron.job row active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
…fallback Semgrep (helmv3-server-action-missing-auth-check) flagged the pre-existing fallback this PR's file touch exposed: when signUp errors already-exists, the action resumed onboarding for ANY caller who knew the email (service-role users lookup + getUserById, no caller verification). Now the caller must either already be signed in as that email (the checkAuth() race case) or sign in with the submitted password (the abandoned-onboarding retry, which also sets their session). Otherwise: the generic already-registered error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fz1ajgmqcEp1yPEzjnXgY5
…e — sync export from 'use server' file breaks the production build Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
… unit tests; re-lock lint ratchet (2275→2158) The prod-only persistence gate correctly blocks admin_events/error_logs writes in the test environment; the writer column-mapping tests now use the documented ADMIN_EVENTS_FORCE_CAPTURE=1 hatch so they keep exercising the real write path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
… console fallback on gated logAdminEvent Adversarial review of the consolidated train caught both: regen-docs.mjs's first-match indexOf locked onto the empty graphql_public schema that the #733 types-regen added above public, silently rewriting the glossary inventory to '0 tables, no enums' — and the self-healing docs workflow would have seen 'in sync' forever. logAdminEvent was the only gated writer with zero off-prod visibility. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
PR title or description contains an excluded keyword. |
|
Adversarial review (13 Sonnet agents, 3 lenses × refuter panels) over the full consolidated diff: Confirmed & fixed in 486145a:
Refuted (no change): TOCTOU read-then-write on the admin-preserve guard — no code path writes |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (24)
Summary by CodeRabbit
WalkthroughThis PR adds runtime telemetry gating (shouldPersistAdminTables) to skip DB writes off-prod, adds retry/remount UX to admin PanelBoundary, introduces an admin-preserving user-row helper for onboarding flows, extends EmptyState to accept React element icons, fixes a docs-generation schema-offset bug, forces dynamic rendering for a demo-sessions page, disables dependabot auto-rebase, updates lint baseline, and adds a pg_cron retention purge plus baseball settings reconciliation migration. ChangesTelemetry Persistence Gating
Admin Panel Retry UX
Admin-Preserving Onboarding Role Writes
EmptyState Icon Flexibility
Docs Generation, Dynamic Rendering, Config, and Migrations
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Route as API Route (log-event/log-error)
participant Gate as shouldPersistAdminTables
participant Logger as admin-logger / server-error-logger
participant DB as admin_events / error_logs
Route->>Gate: shouldPersistAdminTables()
alt gate returns false
Gate-->>Route: false
Route-->>Route: return {success:true, persisted:false}
else gate returns true
Gate-->>Route: true
Route->>Logger: proceed with validation and logging
Logger->>Gate: shouldPersistAdminTables()
Gate-->>Logger: true
Logger->>DB: insert row
DB-->>Logger: ack
Logger-->>Route: success
end
sequenceDiagram
participant User
participant PanelStale
participant PanelRetryButton
participant Router as next/navigation router
participant PanelErrorBoundary
participant PanelBoundary
PanelErrorBoundary->>PanelStale: render error state with action
User->>PanelRetryButton: click "Try again"
PanelRetryButton->>Router: router.refresh() (in transition)
Router-->>PanelRetryButton: refresh complete
PanelRetryButton->>PanelErrorBoundary: call reset()
PanelErrorBoundary->>PanelErrorBoundary: clear error state, call onRetryReset()
PanelErrorBoundary->>PanelBoundary: onRetryReset()
PanelBoundary->>PanelBoundary: increment attempt, key={attempt}
PanelBoundary->>PanelErrorBoundary: remount subtree
Possibly related PRs
Suggested labels: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.44.0)ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml Comment |
PR Summary by QodoConsolidated admin-dashboard fix train: role guard, RSC fix, telemetry gate, schema drift
AI Description
Diagram
High-Level Assessment
Files changed (25)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
93 rules 1. User email drift
|
| if (existing.role === 'admin' || existing.role === role) return { error: null }; | ||
|
|
||
| const { error } = await admin.from('users').update({ role }).eq('id', userId); | ||
| return { error }; |
There was a problem hiding this comment.
2. User email drift 🐞 Bug ≡ Correctness
ensureUserRowPreservingAdmin() returns early when the existing role matches, so it never updates public.users.email for existing rows even if the Auth email has changed. This can break downstream code paths that resolve users by matching public.users.email (e.g., admin briefing links).
Agent Prompt
### Issue description
`ensureUserRowPreservingAdmin()` currently returns early when `existing.role === 'admin' || existing.role === role`, which prevents updating `public.users.email` for existing users. Since the app supports email changes via `supabase.auth.updateUser({ email })`, `public.users.email` can drift and email-based lookups can fail.
### Issue Context
- `public.users.email` is used for resolving user links in admin tooling.
- The helper was introduced to prevent service-role upserts from demoting admins; we should preserve that safety while still syncing email.
### Fix Focus Areas
- src/app/baseball/actions/onboarding.ts[67-90]
### Suggested fix
- Select both `role` and `email` when reading the existing row.
- If the row exists:
- If `existing.email !== email`, update `email`.
- Only update `role` when `existing.role !== 'admin'` and `existing.role !== role`.
- If neither field needs changes, return `{ error: null }`.
- Keep the “never demote admin” guarantee intact.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
One-train consolidation of the 2026-07-03 admin-dashboard fixes
Prod admin dashboard (
/admin,/admin/golf) has been erroring since the Bridge v2 + DB-hardening deploys. This PR consolidates every open fix onto one branch so main gets exactly one Vercel deploy (supersedes #736, #737, #738, #739; includes #734 via the train2 merge).What was actually broken (root-caused)
get_admin_rounds_rollup42501 Forbidden on/admin/golf— everyget_admin_*_rollupRPC gates onusers.role='admin'; baseball onboarding's service-roleusersupsert clobbered the super-admin's row down tocoach. The role was restored live at 2026-07-03 04:18Z; this train makes the demotion impossible to reintroduce (ensureUserRowPreservingAdmin, from P0: stop baseball onboarding demoting admins + honest Bridge panel fallback #736)./golf/dashboardRSC crash (digest 3173807121, "Functions cannot be passed to Client Components") — icon component types crossed the RSC boundary viaEmptyState(from P1: fix /golf/dashboard RSC function-serialization crash (digest 3173807121) #737).shouldPersistAdminTableswas a sync export from a'use server'file. Completed here: moved tosrc/lib/telemetry-gate.ts, all 4 importers repointed, writer tests use theADMIN_EVENTS_FORCE_CAPTURE=1hatch, new gate unit tests.Contents
event_typereconcile migration (already applied to prod, in-repo as source of truth).shouldPersistAdminTablesin its own module), Next control-flow filter, demo-sessions force-dynamic, retention pg_cron mirror.Verification
tsc --noEmit: 0 errors🤖 Generated with Claude Code
https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC