Skip to content

feat(bridge): visibility wave — Utilization, Pulse Grid, Thread, Release Ledger, First 7 Days + tab max-out - #898

Merged
njrini99-code merged 3 commits into
mainfrom
feat/bridge-visibility-wave
Jul 17, 2026
Merged

feat(bridge): visibility wave — Utilization, Pulse Grid, Thread, Release Ledger, First 7 Days + tab max-out#898
njrini99-code merged 3 commits into
mainfrom
feat/bridge-visibility-wave

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Stacked on #897. The launch-month visibility build: feature utilization, team utilization, and release-performance instruments plus every P0/P1 from the 12-tab Bridge audit. Full details in the commit message; highlights:

  • /admin/utilization — Adoption Terrain heat grid + Feature Constellation with power-user cross-highlight (which features are alive, who drives them, what's going cold before launch)
  • /admin/teams — Pulse Grid: every team's 30-day activity/error EKG, worst-first
  • /admin/thread/{user|team}/[id] — one-screen entity timeline (usage, errors they hit, sessions, auth)
  • /admin/deploys — Release Ledger with per-deploy verdict: resolved-and-stayed-quiet vs new fingerprints
  • /admin/auth — First 7 Days activation runway per sport; real failed-login burst detection
  • /admin/lifting — Lift Lab finally visible across Bridge (tab, nav, activity kinds, overview link)
  • Errors tab P0s (Sentry-row filtering), jobs per-job history P0, golf/baseball parity, mobile identity + ⌘K, LocalTime + pagination sweeps

Gates: typecheck / lint / 5,383 unit tests / production build all pass. 11 review findings (3 lenses incl. design taste) fixed in-branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg

Fable Integrator and others added 2 commits July 16, 2026 23:06
Fixes the three real bugs from tonight's incident triage and makes the
Bridge self-cleaning:

- crm-engagement: chunk getCoachEngagement into 150-id batches (the
  single .in() with ~2,300 coach ids built an ~85KB request URL that
  died with an empty-message PostgrestError); per-batch degrade instead
  of all-or-nothing
- coachhelm insights: evidence-refusal guardrail (sample_n < 5) now
  signals via typed InsightEvidenceRefusal and logs as routine
  warning telemetry (skipSentry) instead of a paging error through the
  roster-sweep cron
- useAdminRealtime: unique per-connect channel topic + cleanup catch,
  eliminating the "cannot add postgres_changes callbacks after
  subscribe()" race between async removeChannel and same-name recreate

Auto-resolution (new src/lib/admin/auto-resolve.ts, wired into the
log-retention cron): Rule A resolves error fingerprints with zero
events since the newest READY production deploy once it is 24h old;
Rule B resolves anything quiet 14 days. The bulk UPDATE is bounded by
created_at < the rule's own staleness cutoff, so a fingerprint firing
mid-run can never be swept in. Regressions reappear automatically via
resolved DEFAULT false on fresh inserts. Sentry-sourced rows in the
errors view get the same rule applied at read time (fail-open without
deploy data). Unit tests for both.

Gates: typecheck, lint, unit (5,357 tests), production build — pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
…Release Ledger, First 7 Days + tab-audit P0/P1 fixes

New instruments (all from one admin_events substrate, no new deps):
- /admin/utilization: Adoption Terrain heat grid (85 features x 30d/12w,
  log-scale unique-user cells, dropout-risk rings) + Feature Constellation
  (hot/steady/cold tiles, power-user leaderboard with cross-highlight),
  one shared 84d rollup query, shared FeatureDrawer drill-in
- /admin/deploys: Release Ledger (7d hourly error trend with deploy
  markers) + per-deploy verdict cards (before/after delta, resolved-and-
  stayed-quiet vs new fingerprints, feature deltas); LIVE badge on the
  running deployment; honest CurrentBuildCard unconfigured state
- /admin/teams: Pulse Grid (per-team 30d activity/error EKG, worst-first)
- /admin/thread/{user,team}/[id]: universal entity timeline (events,
  errors they hit, sessions, auth) — the "whole story on one screen" view
- /admin/auth: First 7 Days signup->onboarded->activated runway per sport
  with median time-to-gate; failed-login-only burst detection; sport/type
  filters + email search; chart pagination past the 200-row cap
- /admin/lifting: new tab; Lift Lab kinds in activity feed; nav + overview
  wiring

Wired/organized fixes from the 12-tab audit: errors-tab filters now apply
to Sentry-origin incidents (2x P0), fingerprint detail shows feature/
source/sport/file inline + bulk-resolve, jobs tab per-job-type history
(P0: global top-500 crowded out low-frequency crons) + failure-rate bars,
golf tab parity (PlayerWatchlist + command cards + error-trace links),
baseball pitcher-blind watchlist fix, fetchUsersTab error-checks +
pagination, mobile page identity + reachable command menu, LocalTime
sweep, work-tab truncation disclosure + filters.

Gates: typecheck, lint, unit (5,383), production build - all pass.
11 adversarial-review findings (data honesty, runtime, design taste)
fixed in-branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Too many files changed for review. (66 files found, 50 file limit)

Bypass the limit by tagging @greptile-apps to review.

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 17, 2026 10:36am

Request Review

@supabase

supabase Bot commented Jul 17, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added Lift Lab and Feature Utilization admin dashboards with trends, filters, adoption insights, and recent activity.
    • Added team pulse, entity timeline, release ledger, activation funnel, and roster watchlist views.
    • Added command-menu search access, role/sport/work filters, bulk incident resolution, and clickable user/team navigation.
    • Added richer incident, deployment, authentication, job-run, and activity details.
  • Bug Fixes
    • Improved timestamp localization, data coverage, filtering accuracy, and graceful handling of unavailable monitoring data.
  • Style
    • Added new heatmap, sparkline, status, and trend visualizations.

Walkthrough

The PR expands the admin command center with utilization, lifting, pulse, thread, release, activation, filtering, and incident workflows. It adds server-side data aggregations, interactive dashboards, shared chart primitives, localized timestamps, navigation updates, and validation coverage.

Changes

Shared admin UI and navigation

Layer / File(s) Summary
Shared admin UI and navigation
src/app/admin/_components/*, src/app/admin/activity/*
Adds command-menu access from the More sheet, new lifting/utilization navigation, roster command cards, activity metadata, bulk resolution, starved KPI copy, and shared timestamp rendering.

Auth, activation, and user filtering

Layer / File(s) Summary
Auth, activation, and user filtering
src/app/admin/auth/*, src/app/admin/users/*, src/lib/admin/data/auth.ts, src/lib/admin/data/activation-funnel.ts, src/lib/admin/data/users.ts
Adds URL-driven auth and role filters, activation runway metrics, user links, sport-scoped queries, paginated user data, pitcher attribution, and localized timestamps.

Lifting, pulse, and entity threads

Layer / File(s) Summary
Lifting, pulse, and entity threads
src/app/admin/lifting/*, src/app/admin/teams/*, src/app/admin/thread/*, src/lib/admin/data/lifting.ts, src/lib/admin/data/pulse-grid.ts, src/lib/admin/data/entity-thread.ts, src/lib/admin/data/activity.ts
Adds Lift Lab reporting, team pulse sorting and status views, unified user/team timelines, and lift-session activity ingestion.

Utilization data and visualizations

Layer / File(s) Summary
Utilization data and visualizations
src/app/admin/utilization/*, src/lib/admin/data/feature-adoption.ts, src/components/fairway/charts/*
Adds feature adoption aggregation, heat-grid interactions, power-user cross-highlighting, feature drawers, EKG sparklines, and tone-aware trend markers.

Release ledger and job history

Layer / File(s) Summary
Release ledger and job history
src/app/admin/deploys/*, src/lib/admin/data/release-ledger.ts, src/lib/admin/data/jobs.ts, src/app/admin/jobs/page.tsx
Adds release regression cards, deployment identity and live markers, expandable details, release trends, and per-job recent-run strips.

Incident attribution and error workflows

Layer / File(s) Summary
Incident attribution and error workflows
src/app/admin/errors/*, src/app/admin/golf/page.tsx, src/lib/admin/data/incident-feed.ts, src/lib/admin/data/triage.ts, src/lib/admin/data/overview.ts
Adds Sentry filter construction and attribution hints, richer error context, bulk incident resolution, golf command drilldowns, and explicit Sentry status messaging.

Work log filtering and truncation

Layer / File(s) Summary
Work log filtering and truncation
src/app/admin/work/*, src/lib/admin/github-pr-timeline.ts
Adds area/state URL filters, filtered counts, filter chips, and notices when GitHub results are capped.

Data coverage and supporting validation

Layer / File(s) Summary
Data coverage and supporting validation
src/lib/admin/data/golf.ts, src/lib/admin/data/__tests__/*
Adds paginated golf coverage and deterministic tests for activation, auth bucketing, Sentry queries, release helpers, and triage attribution.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin as UtilizationPage
  participant Data as fetchFeatureAdoption
  participant Grid as AdoptionHeatGrid
  participant Constellation as FeatureConstellation
  Admin->>Data: Fetch feature adoption rows and power users
  Data-->>Admin: Return rows, users, and readouts
  Admin->>Grid: Render adoption heat grid
  Admin->>Constellation: Render filters and leaderboard
  Constellation->>Grid: Open selected feature details
Loading
sequenceDiagram
  participant Admin as AuthPage
  participant Filters as parseAuthFilters
  participant Auth as fetchAuthTab
  participant Funnel as fetchActivationFunnel
  Admin->>Filters: Parse URL filters
  Admin->>Auth: Fetch filtered auth data
  Admin->>Funnel: Fetch activation funnel
  Auth-->>Admin: Return feed and sign-in series
  Funnel-->>Admin: Return sport runway metrics
Loading

Possibly related PRs

Suggested labels: performance

🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is relevant but uses the unsupported scope "bridge" instead of one of the required Conventional Commit scopes. Retitle as Conventional Commits with an allowed scope, e.g. feat(golf): ... or feat(supabase): ..., matching the main change.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly matches the PR’s admin visibility, release, and navigation changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Service-Role In Client Bundles ✅ Passed PASS: no changed file outside the allowed server/admin paths references SUPABASE_SERVICE_ROLE_KEY; changed client components only use type imports.
Rls Coverage On New Tables ✅ Passed No changed migrations/SQL files appear in the merge-base diff, so the CREATE TABLE/RLS policy check is not applicable.
Auth Check In Server Actions ✅ Passed No PR-changed files under src/app//actions//*.ts; the auth-getUser check is not applicable.
Sport-Prefixed Table Names ✅ Passed Checked the changed TS/TSX diff; no bare .from('teams'|'players'|'rounds'|'coaches'|'events') calls were found.
No Destructive Writes ✅ Passed No changed file has a DB deleteinsert rebuild; only URLSearchParams deletes appear in auth/page.tsx:41 and errors/page.tsx:55-74.
No Edits To Historical Migrations ✅ Passed No supabase/migrations files changed in the PR diff; the merged commit only touched app/lib files, so the historical-migration rule is satisfied.
Conventional Commits ✅ Passed PR title and squash subject both start with feat(bridge):, satisfying the Conventional Commits regex in .coderabbit.yaml:405-409.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bridge-visibility-wave
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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.1)

ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@njrini99-code

Copy link
Copy Markdown
Owner Author

🤖 Mission Control — PR summary

What it changes — Bridge "visibility wave": new admin instruments built entirely on the existing admin_events substrate (no new deps):

  • /admin/utilization — Adoption Terrain heat grid (85 features × 30 d / 12 w, log-scale unique-user cells) + Feature Constellation (hot/steady/cold tiles, power-user leaderboard), one shared 84 d rollup.
  • /admin/deploys — Release Ledger (7 d hourly error trend with deploy markers) + per-deploy verdict cards (before/after delta, resolved-and-quiet vs. new fingerprints); LIVE badge on the running deploy.
  • /admin/teams — Pulse Grid (per-team 30 d activity/error EKG, worst-first).
  • /admin/thread/{user,team}/[id] — universal entity timeline.
  • /admin/auth — First 7 Days signup → onboarded → activated funnel.
  • Plus tab-audit P0/P1 fixes across the admin shell and new src/components/fairway/charts (AdoptionHeatGrid, EkgSparkline, TrendChart).

Risk / areas — Platform / admin only (src/app/admin/*, src/lib/admin/data/*, src/components/fairway/charts). Read-only analytics over admin_events; the main risk is heavy rollup query cost.

Reviewers should watch — the shared 84 d Adoption rollup + 7 d hourly Release Ledger query cost, log-scale cell-binning correctness, and that the new admin/data loaders don't reference drifted columns (cf. Incident #896 missing-column errors).

Heads-up — this PR is stacked on #897 (base branch fix/bridge-triage-and-autoresolve, not main) — merge #897 first. CI: required static checks green (ESLint, ast-grep, semgrep, gitleaks, Route Hygiene, shellcheck, sqlfluff); Next build / Unit tests / TypeScript / Supabase RLS still pending at time of writing — no failures.

Base automatically changed from fix/bridge-triage-and-autoresolve to main July 17, 2026 10:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 41

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/admin/users/page.tsx (1)

414-471: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

src/app/admin/users/page.tsx:414-471 — Preserve active filters when submitting search.

The role chips retain q/team/sport/attention, but the GET form submits only q; searching after selecting a role clears that role. Include hidden fields for every active non-search filter.

Proposed fix
 <form method="get" className="flex flex-wrap items-center gap-2">
+  {role && <input type="hidden" name="role" value={role} />}
+  {team && <input type="hidden" name="team" value={team} />}
+  {sport && <input type="hidden" name="sport" value={sport} />}
+  {attention && <input type="hidden" name="attention" value={attention} />}
   <SearchField
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/users/page.tsx` around lines 414 - 471, Update the GET form in
Body to preserve every active non-search filter when submitting: add hidden
fields for role, team, sport, and attention only when each has a value. Keep
SearchField as the visible q input and preserve the existing clear-filter
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/admin/_components/BulkResolveButton.tsx`:
- Around line 34-45: Refactor handleClick to pass an async callback directly to
startTransition, await onResolve(eventIds), and call router.refresh() after
success. Wrap the await flow in try/catch and preserve the existing
error-message fallback when updating setError.

In `@src/app/admin/auth/page.tsx`:
- Around line 263-279: Update the search form around SearchField to use
Next.js’s next/form Form for client-side navigation, and add hidden inputs for
each active filter such as sport and eventType so they are retained when
submitting the email query. Preserve the existing SearchField, submit button,
and clear-filters behavior.

In `@src/app/admin/baseball/page.tsx`:
- Around line 57-58: Remove the fallback="never" prop from both LocalTime usages
in src/app/admin/baseball/page.tsx at lines 57-58 and 127-132. Keep the existing
ternary checks for missing timestamps so LocalTime receives only valid dates,
allowing its default loading state during hydration.

In `@src/app/admin/deploys/_components/ReleaseCard.tsx`:
- Around line 24-28: Replace every noncanonical accent color utility with the
corresponding primary color utility across ReleaseCard, ReleaseCardExpand, and
both commit-link renderings in page.tsx: update border-accent-600/40, all
text-accent-700 uses, and hover:text-accent-700 to primary-* equivalents at
ReleaseCard.tsx lines 24-28, 33-40, 78-83, and 121-126; ReleaseCardExpand.tsx
lines 19-29; and page.tsx lines 188-210 and 265-294.

In `@src/app/admin/golf/page.tsx`:
- Around line 68-73: Replace the fetchErrorsTab call in the Promise.all within
the Golf page with a focused error-summary loader that retrieves only the two
displayed Golf metrics. Add or reuse the appropriate summary loader rather than
loading raw/filtered Sentry data, hourly stats, deployments, incident pages, or
wider-window counts through fetchErrorsTab.
- Around line 107-113: Update the watchlist comparator in the sort within the
golf admin page to use an explicit profile-quality rank for both players,
ensuring comparisons are antisymmetric and consistently prioritize lower-quality
profiles. Preserve the existing errors7d and activity30d sort criteria, and
apply the rank only when profileQuality values differ.

In `@src/app/admin/jobs/page.tsx`:
- Around line 80-82: Update the status summary in the runs display so
non-completed runs are not labeled “ok.” In the JSX rendering the failures
summary, count only completed successful runs as ok and represent running or
other non-completed entries as not failed (for example, 0/N failed), while
preserving failed-run reporting.

In `@src/app/admin/lifting/page.tsx`:
- Around line 14-24: Replace every noncanonical accent-* color with the
corresponding primary-* utility at the affected sites: update the section border
and KeyPanelRule dateline in src/app/admin/lifting/page.tsx (lines 14-24),
Auth/Product lane colors and dateline/detail-action colors in
src/app/admin/thread/[entity]/[id]/page.tsx (lines 22-28 and 106-117), and the
journey-action color in src/app/admin/teams/[id]/page.tsx (lines 145-150).
Preserve the existing styling and behavior while using only the approved
primary-* family.

In `@src/app/admin/teams/page.tsx`:
- Around line 72-73: Update the KPI label associated with criticalCount in the
teams admin page to describe the count as teams with critical errors, not the
number of critical errors. Keep the criticalCount calculation unchanged and
adjust the related label and any accompanying text around lines 88–90 for
accurate wording.

In `@src/app/admin/thread/`[entity]/[id]/page.tsx:
- Around line 115-120: Update ThreadHeader so the team link is sport-aware
instead of always targeting `/admin/teams/{id}`. Derive and return a matching
detailHref only for supported team sports/routes, and omit the action when no
compatible detail page exists, while preserving the existing user-detail link
behavior.

In `@src/app/admin/utilization/FeatureConstellation.tsx`:
- Around line 38-42: Replace all forbidden accent-* and fw-warning utility
classes with equivalent canonical palette utilities, preserving each element’s
existing visual role. Update FeatureConstellation.tsx at lines 38-42 and
207-213, FeatureDrawer.tsx at lines 89-95 and 129-134, page.tsx at lines 12-16,
and AdoptionHeatGrid.tsx at lines 66-70, 177-182, and 192-200; use only
primary-*, destructive, warm-*, and cream-* families, including replacements for
outline-accent-500 and ring-fw-warning.
- Around line 149-153: Update the coldOnly filtering logic in the filtered
useMemo to include features classified as cold, rather than requiring
topPowerUsers.length === 0. Preserve the existing quietDays >= 7 requirement and
the appFilter, tierFilter, and sorting behavior.

In `@src/app/admin/work/page.tsx`:
- Around line 3-4: Re-export PrLifecycleState and WorkArea from the canonical
`@/lib/types` type barrel, then update the imports in the page component to obtain
both types from `@/lib/types` while keeping fetchWorkLog imported from
`@/lib/admin/github-pr-timeline`.
- Around line 99-103: Update the empty-state rendering around filteredEntries
and workLog.truncated so truncated results preserve the existing truncation
warning instead of showing the full-history “No PRs match these filters”
message. When truncated, label the empty state “No matching PRs in the most
recent N,” substituting the applicable data.fetchLimit value; retain the current
message for complete histories.
- Around line 49-74: Update the count inputs used by areaChips and stateChips so
each facet is scoped by the opposing active filter: compute area counts from
entries matching stateFilter, and state counts from entries matching areaFilter.
Preserve the existing chip labels, links, selection state, and positive-count
filtering while deriving closedCount from the filtered state counts.

In `@src/components/fairway/charts/AdoptionHeatGrid.tsx`:
- Around line 193-213: Update the heat-grid cell trigger in the cell rendering
flow and its related popover block to use the shared accessible popover
primitive, or establish equivalent trigger-to-popover semantics with
aria-expanded and aria-controls. Ensure opening moves focus appropriately or
announces the dialog, and closing restores focus to the originating cell while
preserving the existing cell details and positioning behavior.
- Around line 187-190: Update the isDropoutCell calculation in the cells.map
callback to convert quiet days into weekly buckets when operating in 12-week
mode, using Math.ceil(row.quietDays / 7) for the trailing cell count. Preserve
the existing daily behavior for other modes and retain the current cells.length
cap.

In `@src/lib/admin/data/activation-funnel.ts`:
- Around line 151-159: The global ACTIVATION_ROWS_CAP limit in golfActivationMap
truncates matching activation events before every player is evaluated. Replace
the capped query with database-side earliest-event aggregation grouped by
player_id, and apply the same uncapped per-entity earliest-event handling in the
related logic at lines 176–193, preserving the existing activation map output.
- Around line 108-245: Handle Supabase errors before aggregation in
src/lib/admin/data/activation-funnel.ts (lines 108-245): validate results from
fetchGolfWindowStats, fetchBaseballWindowStats, golfActivationMap,
baseballActivationMap, and the signup query in fetchSportRunway, propagating or
throwing errors instead of treating failed data/count values as empty or zero.
Apply the same validation in src/lib/admin/data/auth.ts (lines 161-258) for
daily counts, the main query batch, and the user-email lookup before mapping
results.

In `@src/lib/admin/data/activity.ts`:
- Around line 687-696: Update fetchLiftSessionLogged to use stable composite
pagination: order by created_at descending followed by id descending, and
encode/decode both values in the cursor so filtering returns rows older than the
cursor or with the same timestamp and a lower id. Preserve the existing limit
and teamId filtering behavior.

In `@src/lib/admin/data/entity-thread.ts`:
- Around line 149-158: The identity-resolution flow must not silently convert
lookup failures into missing entities or empty results. In the profile lookup
block around the Promise.all calls, validate each response error and either
throw it or record it in degradedSources; apply the same handling to team
lookups around the affected resolution logic and to resolveTeamUserIds()
failures, preserving explicit degraded-source information instead of returning
an empty roster as success.
- Around line 261-359: Update the golf_roster and baseball_roster source loaders
to apply deterministic descending timestamp ordering before limit(ROSTER_LIMIT),
using the relevant joined_at/created_at field. Track whether each membership or
coach-staff query returns ROSTER_LIMIT rows, and return hitCap: true when any
query reaches the cap; otherwise preserve false. Apply this consistently across
both player and coach branches.
- Around line 237-259: Resolve the user’s Lift Lab athlete record before the
query in sources.lift, then filter helm_lifting_sessions.athlete_id with the
resolved helm_lifting_athletes.id rather than userId. Follow the existing lookup
pattern used by the lifting access/session flows, while preserving the current
mapping and hitCap behavior.

In `@src/lib/admin/data/feature-adoption.ts`:
- Around line 188-192: Update the previous-seven-day window boundaries in the
feature-adoption calculation around prev7Start and prev7End so the exclusive
range contains exactly seven days, including day - 7 and excluding day - 14.
Apply the same correction to the corresponding previous-window filtering or
aggregation logic used to compute uniqueUsersPrev7d, while leaving the current
7-day window unchanged.
- Around line 32-113: Move the shared feature-adoption interfaces from
feature-adoption.ts into the exports of `@/lib/types`, then remove their local
definitions and update FeatureConstellation.tsx to import its application types
from `@/lib/types` and FeatureDrawer.tsx to import FeatureAdoptionRow from
`@/lib/types`; apply these changes in src/lib/admin/data/feature-adoption.ts
(lines 32-113), src/app/admin/utilization/FeatureConstellation.tsx (line 19),
and src/app/admin/utilization/FeatureDrawer.tsx (line 6).
- Around line 188-208: The feature-adoption flow repeatedly scans up to 84 days
of raw events on every refresh. In src/lib/admin/data/feature-adoption.ts lines
188-208, replace fetchAllRows in the feature-adoption loader with a shared
cached, RPC, or materialized daily rollup and refresh only recent buckets. In
src/app/admin/utilization/page.tsx line 10, stop bypassing the shared cache on
every render; at line 103, refresh cached data or recent deltas instead of
triggering another full 84-day scan.

In `@src/lib/admin/data/golf.ts`:
- Around line 98-123: Update the parallel results handling around the two
fetchAllRowsResult calls so membersRes.error and errorRes.error are checked
before aggregating counts. Propagate either pagination failure or mark the
corresponding roster/error metric stale instead of treating partial rows as
complete; preserve normal aggregation when both requests succeed.

In `@src/lib/admin/data/incident-feed.ts`:
- Around line 197-235: Update the merge attribution argument in
buildIncidentFeedFromSources so filters.sport and filters.feature are provided
only when filteredSentry exists with status === 'ok'; when falling back to the
raw sentry result, pass null hints instead. Preserve the existing raw fallback
for availability, but do not label or treat unfiltered fallback issues as
matching the active filtered tags.

In `@src/lib/admin/data/lifting.ts`:
- Around line 93-124: Replace the repeated fetchAllRowsResult calls in the
lifting data aggregation with a database RPC or view that performs the 84-day
weekly session counts and 30-day COUNT(DISTINCT athlete_id) in SQL. Update the
surrounding Promise.all flow to consume the aggregated result while preserving
sessionsToday, sessionsThisWeek, sessionsByWeek, and active-athlete outputs;
leave the PR and program count queries unchanged.
- Around line 77-91: The lifting metrics currently use rolling day windows
instead of calendar-week boundaries. Update the date setup around daysAgoIso,
including the affected “this week” calculations and bucket construction, to
derive the current and previous Monday boundaries using the existing weekStart()
contract, then build exactly 12 Monday-based buckets so partial and zero-count
weeks are represented correctly; remove or replace the 7/14/84-day threshold
logic.
- Around line 138-150: Update fetchLiftingTab to check prsThisWeekRes.error,
prs30dRes.error, and activeProgramsRes.error before their values fall back to
zero. Throw descriptive errors containing each query’s error message, matching
the existing fail-loud checks for sessionsRes, activeAthletesRes, and
recentSessionsRes.

In `@src/lib/admin/data/pulse-grid.ts`:
- Around line 147-191: Update the Pulse Grid data-loading flow around the
Promise.all results, including the additional event, round, game, and lift
queries, to inspect every query result for errors before constructing telemetry.
Propagate any failure to PanelBoundary by throwing, or return explicit
degraded-source metadata; do not replace failed results with empty arrays or
silently continue. Use the existing result symbols such as golfTeamsRes and
baseballTeamsRes consistently across the affected query groups.
- Around line 225-288: Bound the Pulse Grid data-loading queries in the
Promise.all block, replacing the unbounded userIdList .in filters used by the
login and fallback-error fetches with a database-side aggregate RPC/view where
available; otherwise chunk userIdList into bounded batches and combine their
results. Preserve the existing 30-day filtering and aggregation behavior while
ensuring each request has a controlled ID and row scope.
- Around line 333-336: Clamp daysSinceActivity to a minimum of zero in the
activity-age calculation before passing it to classifyHalo. Update the
expression in the pulse-grid data flow while preserving WINDOW_DAYS for missing
activity dates and the existing UTC date calculation.

In `@src/lib/admin/data/release-ledger.ts`:
- Around line 47-108: Move the exported release-ledger contracts from
src/lib/admin/data/release-ledger.ts lines 47-108 into `@/lib/types`, and update
src/app/admin/deploys/_components/ReleaseCard.tsx line 4 to import
ReleaseCardData from that canonical module. Move CronRunSummary and CronBoardRow
from src/lib/admin/data/jobs.ts lines 10-25 into `@/lib/types`, then update
src/app/admin/jobs/page.tsx line 2 to import those job types from `@/lib/types`.
- Around line 416-424: Update the deploy-marker assignment in the cards loop to
preserve every production deploy falling within the same hourly bucket instead
of overwriting bucket.marker. Change the bucket marker representation and chart
consumption as needed to support multiple markers or distinct deployment
timestamps, while retaining each marker’s label and tone.
- Around line 138-161: The release-fetching flow currently limits results before
production filtering and deduplication, potentially omitting valid releases.
Update the logic around fetchVercelDeployments and the admin_events query to
retrieve enough records, filter to production deploys, and deduplicate repeated
deploys or boot markers before applying the final cap of 20; continue until at
least 10 distinct production deploys are available when possible.
- Around line 156-176: Propagate backend query failures instead of treating them
as empty admin results. In src/lib/admin/data/release-ledger.ts:156-176, ensure
the admin_events query error is checked and thrown or returned as an
unavailable/error result consumed by the release ledger flow. In
src/lib/admin/data/jobs.ts:86-110, update fetchJobsTab() to check errors from
all job and summary queries and propagate an unavailable/error result so the
existing boundary surfaces the failure rather than zero counts.

In `@src/lib/admin/data/users.ts`:
- Around line 354-362: Update the admin_events query inside fetchAllRowsResult
to conditionally filter by filters.sport using an equality constraint when the
sport filter is provided, while preserving the existing unfiltered behavior when
it is absent.
- Around line 275-276: Update the includeGolf and includeBaseball branches in
the users data-loading flow to fetch team records through fetchAllRowsResult
instead of bare admin.from selects. Apply ascending id ordering to both queries,
while preserving emptyRows when the corresponding team type is excluded.

In `@src/lib/admin/github-pr-timeline.ts`:
- Around line 239-244: Update the result-building logic around the snapshot
fetch and truncated flag so a full page alone does not imply truncation. Fetch
one additional pull beyond the configured limit or use GitHub pagination
metadata to determine whether more results exist, then set truncated only when
the history is actually incomplete while preserving the existing limit for
returned pulls and the ok(snapshot) result structure.

---

Outside diff comments:
In `@src/app/admin/users/page.tsx`:
- Around line 414-471: Update the GET form in Body to preserve every active
non-search filter when submitting: add hidden fields for role, team, sport, and
attention only when each has a value. Keep SearchField as the visible q input
and preserve the existing clear-filter behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a057e58-3a8e-4257-9f04-6647fda71497

📥 Commits

Reviewing files that changed from the base of the PR and between eb406b0 and 3a463f5.

📒 Files selected for processing (66)
  • src/app/admin/_components/AdminShell.tsx
  • src/app/admin/_components/AdminStatusBanner.tsx
  • src/app/admin/_components/BulkResolveButton.tsx
  • src/app/admin/_components/KpiTile.tsx
  • src/app/admin/_components/PlayerWatchlist.tsx
  • src/app/admin/_components/SessionsPanel.tsx
  • src/app/admin/_components/TeamCommandCard.tsx
  • src/app/admin/_components/__tests__/admin-nav.test.ts
  • src/app/admin/_components/admin-nav.ts
  • src/app/admin/_components/player-tone.ts
  • src/app/admin/activity/__tests__/kind-meta.test.ts
  • src/app/admin/activity/kind-meta.ts
  • src/app/admin/auth/ActivationRunway.tsx
  • src/app/admin/auth/AuthFilterChips.tsx
  • src/app/admin/auth/page.tsx
  • src/app/admin/baseball/page.tsx
  • src/app/admin/deploys/_components/ReleaseCard.tsx
  • src/app/admin/deploys/_components/ReleaseCardExpand.tsx
  • src/app/admin/deploys/_components/ReleaseLedger.tsx
  • src/app/admin/deploys/page.tsx
  • src/app/admin/errors/[fingerprint]/page.tsx
  • src/app/admin/errors/page.tsx
  • src/app/admin/golf/page.tsx
  • src/app/admin/health/page.tsx
  • src/app/admin/jobs/page.tsx
  • src/app/admin/lifting/page.tsx
  • src/app/admin/page.tsx
  • src/app/admin/teams/TeamsSortChips.tsx
  • src/app/admin/teams/[id]/page.tsx
  • src/app/admin/teams/page.tsx
  • src/app/admin/thread/[entity]/[id]/page.tsx
  • src/app/admin/users/UserRoleFilterChips.tsx
  • src/app/admin/users/[id]/page.tsx
  • src/app/admin/users/page.tsx
  • src/app/admin/utilization/FeatureConstellation.tsx
  • src/app/admin/utilization/FeatureDrawer.tsx
  • src/app/admin/utilization/page.tsx
  • src/app/admin/work/WorkFilterChips.tsx
  • src/app/admin/work/WorkTimeline.tsx
  • src/app/admin/work/page.tsx
  • src/components/fairway/charts/AdoptionHeatGrid.tsx
  • src/components/fairway/charts/EkgSparkline.tsx
  • src/components/fairway/charts/TrendChart.tsx
  • src/components/fairway/charts/index.ts
  • src/components/fairway/index.ts
  • src/lib/admin/data/__tests__/activation-funnel.test.ts
  • src/lib/admin/data/__tests__/auth.test.ts
  • src/lib/admin/data/__tests__/incident-feed.test.ts
  • src/lib/admin/data/__tests__/release-ledger.test.ts
  • src/lib/admin/data/__tests__/triage.test.ts
  • src/lib/admin/data/activation-funnel.ts
  • src/lib/admin/data/activity.ts
  • src/lib/admin/data/auth.ts
  • src/lib/admin/data/entity-thread.ts
  • src/lib/admin/data/errors.ts
  • src/lib/admin/data/feature-adoption.ts
  • src/lib/admin/data/golf.ts
  • src/lib/admin/data/incident-feed.ts
  • src/lib/admin/data/jobs.ts
  • src/lib/admin/data/lifting.ts
  • src/lib/admin/data/overview.ts
  • src/lib/admin/data/pulse-grid.ts
  • src/lib/admin/data/release-ledger.ts
  • src/lib/admin/data/triage.ts
  • src/lib/admin/data/users.ts
  • src/lib/admin/github-pr-timeline.ts

Comment on lines +34 to +45
function handleClick() {
setError(null);
startTransition(() => {
void onResolve(eventIds)
.then(() => {
router.refresh();
})
.catch((err: unknown) => {
setError(err instanceof Error ? err.message : 'Bulk resolve failed — try again');
});
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Leverage React 19's async transitions for cleaner control flow.

React 19 natively supports passing async functions to startTransition, allowing you to replace the .then().catch() chain with a standard try/catch block. This mirrors the pattern already used in SessionsPanel.tsx.

♻️ Proposed refactor
-  function handleClick() {
-    setError(null);
-    startTransition(() => {
-      void onResolve(eventIds)
-        .then(() => {
-          router.refresh();
-        })
-        .catch((err: unknown) => {
-          setError(err instanceof Error ? err.message : 'Bulk resolve failed — try again');
-        });
-    });
-  }
+  function handleClick() {
+    setError(null);
+    startTransition(async () => {
+      try {
+        await onResolve(eventIds);
+        router.refresh();
+      } catch (err: unknown) {
+        setError(err instanceof Error ? err.message : 'Bulk resolve failed — try again');
+      }
+    });
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function handleClick() {
setError(null);
startTransition(() => {
void onResolve(eventIds)
.then(() => {
router.refresh();
})
.catch((err: unknown) => {
setError(err instanceof Error ? err.message : 'Bulk resolve failed — try again');
});
});
}
function handleClick() {
setError(null);
startTransition(async () => {
try {
await onResolve(eventIds);
router.refresh();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Bulk resolve failed — try again');
}
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/_components/BulkResolveButton.tsx` around lines 34 - 45,
Refactor handleClick to pass an async callback directly to startTransition,
await onResolve(eventIds), and call router.refresh() after success. Wrap the
await flow in try/catch and preserve the existing error-message fallback when
updating setError.

Comment on lines +263 to +279
<form method="get" className="flex flex-wrap items-center gap-2">
<SearchField
name="q"
defaultValue={filters.q ?? ''}
placeholder="Search email…"
aria-label="Search auth events and lockouts by email"
wrapperClassName="max-w-xs"
/>
<Button type="submit" variant="secondary" size="sm">
Search
</Button>
{hasActiveFilters ? (
<Button asChild variant="ghost" size="sm">
<Link href={clearFiltersHref()}>Clear filters</Link>
</Button>
) : null}
</form>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve active filters during search and use client-side navigation.

A standard <form method="get"> discards existing query parameters (such as sport and eventType) when submitted. Add hidden inputs so that active filters survive an email search. Furthermore, consider upgrading to Next.js 15's <Form> (next/form) to enable client-side navigation without full page reloads.

🛠️ Proposed fix

Update your imports at the top of the file to include Form:

import Form from 'next/form';

Then apply this fix to the search form:

-        <form method="get" className="flex flex-wrap items-center gap-2">
+        <Form action="/admin/auth" className="flex flex-wrap items-center gap-2">
+          {filters.sport ? <input type="hidden" name="sport" value={filters.sport} /> : null}
+          {filters.eventType ? <input type="hidden" name="eventType" value={filters.eventType} /> : null}
           <SearchField
             name="q"
             defaultValue={filters.q ?? ''}
             placeholder="Search email…"
             aria-label="Search auth events and lockouts by email"
             wrapperClassName="max-w-xs"
           />
           <Button type="submit" variant="secondary" size="sm">
             Search
           </Button>
           {hasActiveFilters ? (
             <Button asChild variant="ghost" size="sm">
               <Link href={clearFiltersHref()}>Clear filters</Link>
             </Button>
           ) : null}
-        </form>
+        </Form>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form method="get" className="flex flex-wrap items-center gap-2">
<SearchField
name="q"
defaultValue={filters.q ?? ''}
placeholder="Search email…"
aria-label="Search auth events and lockouts by email"
wrapperClassName="max-w-xs"
/>
<Button type="submit" variant="secondary" size="sm">
Search
</Button>
{hasActiveFilters ? (
<Button asChild variant="ghost" size="sm">
<Link href={clearFiltersHref()}>Clear filters</Link>
</Button>
) : null}
</form>
<Form action="/admin/auth" className="flex flex-wrap items-center gap-2">
{filters.sport ? <input type="hidden" name="sport" value={filters.sport} /> : null}
{filters.eventType ? <input type="hidden" name="eventType" value={filters.eventType} /> : null}
<SearchField
name="q"
defaultValue={filters.q ?? ''}
placeholder="Search email…"
aria-label="Search auth events and lockouts by email"
wrapperClassName="max-w-xs"
/>
<Button type="submit" variant="secondary" size="sm">
Search
</Button>
{hasActiveFilters ? (
<Button asChild variant="ghost" size="sm">
<Link href={clearFiltersHref()}>Clear filters</Link>
</Button>
) : null}
</Form>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/auth/page.tsx` around lines 263 - 279, Update the search form
around SearchField to use Next.js’s next/form Form for client-side navigation,
and add hidden inputs for each active filter such as sport and eventType so they
are retained when submitting the email query. Preserve the existing SearchField,
submit button, and clear-filters behavior.

Comment on lines +57 to +58
{team.playerCount} players · last{' '}
{team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" fallback="never" /> : 'never'}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove fallback="never" to prevent inaccurate SSR flashes for active entities.

The LocalTime component uses the fallback prop strictly for the pre-hydration SSR phase while it waits for the client to compute the timezone. Because you are already gating missing dates with a ternary, the <LocalTime> component only receives valid timestamps.

By passing fallback="never", you force the server to render "never" for active teams and players, which then incorrectly flashes on the client before hydrating to the real date. Remove the prop to allow LocalTime to safely use its default loading state (e.g., '—').

  • src/app/admin/baseball/page.tsx#L57-L58: Remove the fallback="never" prop.
  • src/app/admin/baseball/page.tsx#L127-L132: Remove the fallback="never" prop.
🐛 Proposed fixes

For the team row (src/app/admin/baseball/page.tsx:58):

-            {team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" fallback="never" /> : 'never'}
+            {team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" /> : 'never'}

For the player row (src/app/admin/baseball/page.tsx:128-132):

                 {player.lastActivity ?? player.lastSeen ? (
-                  <LocalTime iso={(player.lastActivity ?? player.lastSeen) as string} variant="date" fallback="never" />
+                  <LocalTime iso={(player.lastActivity ?? player.lastSeen) as string} variant="date" />
                 ) : (
                   'never'
                 )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{team.playerCount} players · last{' '}
{team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" fallback="never" /> : 'never'}
{team.playerCount} players · last{' '}
{team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" /> : 'never'}
Suggested change
{team.playerCount} players · last{' '}
{team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" fallback="never" /> : 'never'}
last{' '}
{player.lastActivity ?? player.lastSeen ? (
<LocalTime iso={(player.lastActivity ?? player.lastSeen) as string} variant="date" />
) : (
'never'
)}
📍 Affects 1 file
  • src/app/admin/baseball/page.tsx#L57-L58 (this comment)
  • src/app/admin/baseball/page.tsx#L127-L132
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/baseball/page.tsx` around lines 57 - 58, Remove the
fallback="never" prop from both LocalTime usages in
src/app/admin/baseball/page.tsx at lines 57-58 and 127-132. Keep the existing
ternary checks for missing timestamps so LocalTime receives only valid dates,
allowing its default loading state during hydration.

Comment on lines +24 to +28
<Surface
padding="sm"
elevation={card.isLive ? 'shadow' : 'border'}
className={card.isLive ? 'border-accent-600/40' : undefined}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace accent-* with canonical primary-* tokens.

The changed admin UI introduces a noncanonical color family.

  • src/app/admin/deploys/_components/ReleaseCard.tsx#L24-L28: replace border-accent-600/40.
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L33-L40: replace text-accent-700.
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L78-L83: replace text-accent-700.
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L121-L126: replace text-accent-700.
  • src/app/admin/deploys/_components/ReleaseCardExpand.tsx#L19-L29: replace hover:text-accent-700.
  • src/app/admin/deploys/page.tsx#L188-L210: replace the commit link’s text-accent-700.
  • src/app/admin/deploys/page.tsx#L265-L294: replace the mobile commit link’s text-accent-700.

As per coding guidelines, “Use only canonical color families: primary-*, destructive, warm-*, and cream-*.”

📍 Affects 3 files
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L24-L28 (this comment)
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L33-L40
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L78-L83
  • src/app/admin/deploys/_components/ReleaseCard.tsx#L121-L126
  • src/app/admin/deploys/_components/ReleaseCardExpand.tsx#L19-L29
  • src/app/admin/deploys/page.tsx#L188-L210
  • src/app/admin/deploys/page.tsx#L265-L294
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/deploys/_components/ReleaseCard.tsx` around lines 24 - 28,
Replace every noncanonical accent color utility with the corresponding primary
color utility across ReleaseCard, ReleaseCardExpand, and both commit-link
renderings in page.tsx: update border-accent-600/40, all text-accent-700 uses,
and hover:text-accent-700 to primary-* equivalents at ReleaseCard.tsx lines
24-28, 33-40, 78-83, and 121-126; ReleaseCardExpand.tsx lines 19-29; and
page.tsx lines 188-210 and 265-294.

Source: Coding guidelines

Comment on lines +68 to +73
const [tab, featureHealth, usersTab, errorsTab] = await Promise.all([
fetchGolfTab(),
fetchFeatureHealth(),
fetchUsersTab({ sport: 'golf' }),
fetchErrorsTab({ sport: 'golf', windowHours: 168 }),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Avoid loading the entire Errors tab for two Golf metrics.

src/app/admin/golf/page.tsx:72 invokes raw/filtered Sentry pulls, hourly stats, deployments, incident pagination, and wider-window counts. Add a focused Golf error-summary loader for the two displayed values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/golf/page.tsx` around lines 68 - 73, Replace the fetchErrorsTab
call in the Promise.all within the Golf page with a focused error-summary loader
that retrieves only the two displayed Golf metrics. Add or reuse the appropriate
summary loader rather than loading raw/filtered Sentry data, hourly stats,
deployments, incident pages, or wider-window counts through fetchErrorsTab.

Comment on lines +138 to +161
const vercelRes = await fetchVercelDeployments(20);
if (vercelRes.status === 'ok' && vercelRes.data) {
const prod = vercelRes.data
.filter((d) => d.target === 'production')
.sort((a, b) => b.createdAt - a.createdAt)
.map(
(d): RawReleaseDeploy => ({
uid: d.uid,
commitSha: d.commitSha,
commitMessage: d.commitMessage,
commitRef: d.commitRef,
commitAuthor: d.commitAuthor,
createdAt: d.createdAt,
}),
);
return { deploys: prod, source: 'vercel' };
}

const { data } = await admin
.from('admin_events')
.select('id, created_at, message, metadata')
.eq('event_type', 'deploy')
.order('created_at', { ascending: false })
.limit(20);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Apply limits after production filtering and deploy deduplication.

src/lib/admin/data/release-ledger.ts:138-161 caps both sources at 20 before downstream filtering/deduplication. Preview deploys or repeated boot markers can consume the entire window, causing valid production releases to disappear. Filter/paginate to at least 10 distinct production deploys before applying the final cap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/data/release-ledger.ts` around lines 138 - 161, The
release-fetching flow currently limits results before production filtering and
deduplication, potentially omitting valid releases. Update the logic around
fetchVercelDeployments and the admin_events query to retrieve enough records,
filter to production deploys, and deduplicate repeated deploys or boot markers
before applying the final cap of 20; continue until at least 10 distinct
production deploys are available when possible.

Comment on lines +156 to +176
const { data } = await admin
.from('admin_events')
.select('id, created_at, message, metadata')
.eq('event_type', 'deploy')
.order('created_at', { ascending: false })
.limit(20);

const strOrNull = (v: unknown): string | null => (typeof v === 'string' && v.length > 0 ? v : null);
const mapped = (data ?? []).map((row): RawReleaseDeploy => {
const meta = (row.metadata ?? {}) as Record<string, unknown>;
const createdMs = row.created_at ? Date.parse(row.created_at) : NaN;
return {
uid: row.id,
commitSha: strOrNull(meta.sha),
commitMessage: row.message,
commitRef: strOrNull(meta.ref),
commitAuthor: strOrNull(meta.author),
createdAt: Number.isFinite(createdMs) ? createdMs : Date.now(),
};
});
return { deploys: mapped, source: 'marker-fallback' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline src/lib/admin/data/release-ledger.ts --match 'resolveReleaseDeploys|fetchReleaseLedger' --view expanded
ast-grep outline src/lib/admin/data/jobs.ts --match fetchJobsTab --view expanded
rg -n -C3 "if \\(.*\\.error|throw .*error|return .*error" src/lib/admin src/app/admin

Repository: njrini99-code/helmv3

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- release-ledger outline ---'
ast-grep outline src/lib/admin/data/release-ledger.ts --view expanded

echo '--- jobs outline ---'
ast-grep outline src/lib/admin/data/jobs.ts --view expanded

echo '--- release-ledger slice ---'
sed -n '135,230p' src/lib/admin/data/release-ledger.ts | cat -n

echo '--- jobs slice ---'
sed -n '73,170p' src/lib/admin/data/jobs.ts | cat -n

echo '--- fetch-result slice ---'
sed -n '1,120p' src/lib/admin/fetch-result.ts | cat -n

Repository: njrini99-code/helmv3

Length of output: 14053


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- release-ledger relevant lines ---\n'
sed -n '135,185p' src/lib/admin/data/release-ledger.ts | cat -n

printf '\n--- jobs relevant lines ---\n'
sed -n '73,140p' src/lib/admin/data/jobs.ts | cat -n

Repository: njrini99-code/helmv3

Length of output: 6156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- fetchReleaseLedger slice ---\n'
sed -n '214,320p' src/lib/admin/data/release-ledger.ts | cat -n

printf '\n--- fetchJobsTab callers ---\n'
rg -n -C3 "fetchJobsTab\\(" src/app src/lib | head -n 40

printf '\n--- release ledger caller ---\n'
rg -n -C3 "fetchReleaseLedger\\(" src/app src/lib | head -n 40

Repository: njrini99-code/helmv3

Length of output: 6812


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '330,430p' src/app/admin/jobs/page.tsx | cat -n

Repository: njrini99-code/helmv3

Length of output: 4788


Propagate Supabase failures instead of collapsing them into empty admin data

  • src/lib/admin/data/release-ledger.ts:22-42 drops the admin_events error; a failed deploy-marker query becomes [], then src/lib/admin/data/release-ledger.ts:20-22 returns unconfigured and src/app/admin/deploys/_components/ReleaseLedger.tsx:20-29 can show an empty state for a backend failure.
  • src/lib/admin/data/jobs.ts:14-85 never checks any error from the 18 job queries or the 4 summary queries, so a partial outage renders as zero runs / zero counts in src/app/admin/jobs/page.tsx:46-98.

Throw on any query error here, or change fetchJobsTab() to return an unavailable/error result so the existing boundary can surface the failure.

📍 Affects 2 files
  • src/lib/admin/data/release-ledger.ts#L156-L176 (this comment)
  • src/lib/admin/data/jobs.ts#L86-L110
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/data/release-ledger.ts` around lines 156 - 176, Propagate
backend query failures instead of treating them as empty admin results. In
src/lib/admin/data/release-ledger.ts:156-176, ensure the admin_events query
error is checked and thrown or returned as an unavailable/error result consumed
by the release ledger flow. In src/lib/admin/data/jobs.ts:86-110, update
fetchJobsTab() to check errors from all job and summary queries and propagate an
unavailable/error result so the existing boundary surfaces the failure rather
than zero counts.

Comment on lines +416 to +424
for (const card of cards) {
if (card.createdAt < trendStart) continue;
const idx = bucketIndex.get(bucketStartMs(card.createdAt));
const bucket = idx !== undefined ? buckets[idx] : undefined;
if (!bucket) continue;
bucket.marker = {
label: card.commitSha ? card.commitSha.slice(0, 7) : card.uid.slice(0, 7),
tone: card.verdict.tone,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve every deploy marker within an hourly bucket.

src/lib/admin/data/release-ledger.ts:416-424 assigns a single bucket.marker; a second deploy in the same hour overwrites the first, contradicting the “marker at every production deploy” contract. Store multiple markers per bucket or use unique deployment timestamps on the chart.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/data/release-ledger.ts` around lines 416 - 424, Update the
deploy-marker assignment in the cards loop to preserve every production deploy
falling within the same hourly bucket instead of overwriting bucket.marker.
Change the bucket marker representation and chart consumption as needed to
support multiple markers or distinct deployment timestamps, while retaining each
marker’s label and tone.

Comment on lines 354 to 362
fetchAllRowsResult((from, to) =>
admin
.from('golf_rounds')
.select('team_id, player_id, created_at, total_score, score_to_par')
.gte('created_at', ago30d)
.order('created_at', { ascending: false })
.order('id', { ascending: true })
.range(from, to),
),
admin.from('baseball_games').select('team_id, created_at').order('created_at', { ascending: false }).limit(1000),
fetchAllRowsResult((from, to) =>
admin
.from('baseball_pitch_events')
.select('team_id, player_id, created_at')
.gte('created_at', ago30d)
.order('created_at', { ascending: false })
.order('id', { ascending: true })
.range(from, to),
),
fetchAllRowsResult((from, to) =>
admin
.from('baseball_player_timeline_events')
.select('team_id, player_id, occurred_at, event_type')
.gte('occurred_at', ago30d)
.order('occurred_at', { ascending: false })
.from('admin_events')
.select('team_id, sport, user_id')
.eq('event_type', 'error')
.gte('created_at', ago7d)
.order('id', { ascending: true })
.range(from, to),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

src/lib/admin/data/users.ts:354-362 — Apply filters.sport to the error scan.

Sport-scoped calls skip the opposite roster/activity branches but still paginate every error event across both sports. Add .eq('sport', filters.sport) when provided to avoid an unnecessary request-path row dump.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/data/users.ts` around lines 354 - 362, Update the admin_events
query inside fetchAllRowsResult to conditionally filter by filters.sport using
an equality constraint when the sport filter is provided, while preserving the
existing unfiltered behavior when it is absent.

Comment on lines +239 to +244
// `pulls.length >= limit` means the fetch hit the GITHUB_PR_FETCH_LIMIT
// ceiling, not that the repo's PR history genuinely ends there — without
// this flag, `counts.total` on the "PRs tracked" tile reads as a complete
// count when it's really "most recent N".
const result = ok(snapshot);
return pulls.length >= limit ? { ...result, truncated: true } : result;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
file="$(fd -a 'github-pr-timeline.ts' | head -n1)"
ast-grep outline "$file" --match 'fetchPullRequests|fetchWorkLog' --view expanded
rg -n -C5 '\bfetchPullRequests\s*\(' "$file"

Repository: njrini99-code/helmv3

Length of output: 1047


🏁 Script executed:

#!/bin/bash
file="src/lib/admin/github-pr-timeline.ts"
sed -n '128,260p' "$file"

Repository: njrini99-code/helmv3

Length of output: 4042


🏁 Script executed:

#!/bin/bash
file="src/lib/admin/github-pr-timeline.ts"
rg -n -C3 'function prFetchLimit|prFetchLimit\(' "$file"

Repository: njrini99-code/helmv3

Length of output: 618


src/lib/admin/github-pr-timeline.ts:239-244 — pulls.length >= limit overstates truncation.
A full page is indistinguishable from an exact match set here, so truncated can be set on complete results. Fetch limit + 1 or use GitHub pagination metadata before marking the snapshot truncated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/github-pr-timeline.ts` around lines 239 - 244, Update the
result-building logic around the snapshot fetch and truncated flag so a full
page alone does not imply truncation. Fetch one additional pull beyond the
configured limit or use GitHub pagination metadata to determine whether more
results exist, then set truncated only when the history is actually incomplete
while preserving the existing limit for returned pulls and the ok(snapshot)
result structure.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +275 to +276
includeGolf ? admin.from('golf_teams').select('id, name') : emptyRows,
includeBaseball ? admin.from('baseball_teams').select('id, name') : emptyRows,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

src/lib/admin/data/users.ts:275-276 — Paginate the team tables too.

These two bare selects remain subject to PostgREST’s row cap, so organizations beyond 1,000 teams disappear while their paginated membership rows remain. Fetch both tables through fetchAllRowsResult, ordered by id.

Proposed fix
-includeGolf ? admin.from('golf_teams').select('id, name') : emptyRows,
-includeBaseball ? admin.from('baseball_teams').select('id, name') : emptyRows,
+includeGolf
+  ? fetchAllRowsResult((from, to) =>
+      admin.from('golf_teams').select('id, name').order('id').range(from, to),
+    )
+  : emptyRows,
+includeBaseball
+  ? fetchAllRowsResult((from, to) =>
+      admin.from('baseball_teams').select('id, name').order('id').range(from, to),
+    )
+  : emptyRows,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
includeGolf ? admin.from('golf_teams').select('id, name') : emptyRows,
includeBaseball ? admin.from('baseball_teams').select('id, name') : emptyRows,
includeGolf
? fetchAllRowsResult((from, to) =>
admin.from('golf_teams').select('id, name').order('id').range(from, to),
)
: emptyRows,
includeBaseball
? fetchAllRowsResult((from, to) =>
admin.from('baseball_teams').select('id, name').order('id').range(from, to),
)
: emptyRows,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/admin/data/users.ts` around lines 275 - 276, Update the includeGolf
and includeBaseball branches in the users data-loading flow to fetch team
records through fetchAllRowsResult instead of bare admin.from selects. Apply
ascending id ordering to both queries, while preserving emptyRows when the
corresponding team type is excluded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant