feat(bridge): visibility wave — Utilization, Pulse Grid, Thread, Release Ledger, First 7 Days + tab max-out - #898
Conversation
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
|
Too many files changed for review. ( Bypass the limit by tagging |
|
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 reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Summary by CodeRabbit
WalkthroughThe 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. ChangesShared admin UI and navigation
Auth, activation, and user filtering
Lifting, pulse, and entity threads
Utilization data and visualizations
Release ledger and job history
Incident attribution and error workflows
Work log filtering and truncation
Data coverage and supporting validation
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
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
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 10 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (10 passed)
✨ 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.1)ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml 🔧 ESLint
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. Comment |
|
🤖 Mission Control — PR summary What it changes — Bridge "visibility wave": new admin instruments built entirely on the existing
Risk / areas — Platform / admin only ( 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 Heads-up — this PR is stacked on #897 (base branch |
There was a problem hiding this comment.
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 winsrc/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 onlyq; 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
📒 Files selected for processing (66)
src/app/admin/_components/AdminShell.tsxsrc/app/admin/_components/AdminStatusBanner.tsxsrc/app/admin/_components/BulkResolveButton.tsxsrc/app/admin/_components/KpiTile.tsxsrc/app/admin/_components/PlayerWatchlist.tsxsrc/app/admin/_components/SessionsPanel.tsxsrc/app/admin/_components/TeamCommandCard.tsxsrc/app/admin/_components/__tests__/admin-nav.test.tssrc/app/admin/_components/admin-nav.tssrc/app/admin/_components/player-tone.tssrc/app/admin/activity/__tests__/kind-meta.test.tssrc/app/admin/activity/kind-meta.tssrc/app/admin/auth/ActivationRunway.tsxsrc/app/admin/auth/AuthFilterChips.tsxsrc/app/admin/auth/page.tsxsrc/app/admin/baseball/page.tsxsrc/app/admin/deploys/_components/ReleaseCard.tsxsrc/app/admin/deploys/_components/ReleaseCardExpand.tsxsrc/app/admin/deploys/_components/ReleaseLedger.tsxsrc/app/admin/deploys/page.tsxsrc/app/admin/errors/[fingerprint]/page.tsxsrc/app/admin/errors/page.tsxsrc/app/admin/golf/page.tsxsrc/app/admin/health/page.tsxsrc/app/admin/jobs/page.tsxsrc/app/admin/lifting/page.tsxsrc/app/admin/page.tsxsrc/app/admin/teams/TeamsSortChips.tsxsrc/app/admin/teams/[id]/page.tsxsrc/app/admin/teams/page.tsxsrc/app/admin/thread/[entity]/[id]/page.tsxsrc/app/admin/users/UserRoleFilterChips.tsxsrc/app/admin/users/[id]/page.tsxsrc/app/admin/users/page.tsxsrc/app/admin/utilization/FeatureConstellation.tsxsrc/app/admin/utilization/FeatureDrawer.tsxsrc/app/admin/utilization/page.tsxsrc/app/admin/work/WorkFilterChips.tsxsrc/app/admin/work/WorkTimeline.tsxsrc/app/admin/work/page.tsxsrc/components/fairway/charts/AdoptionHeatGrid.tsxsrc/components/fairway/charts/EkgSparkline.tsxsrc/components/fairway/charts/TrendChart.tsxsrc/components/fairway/charts/index.tssrc/components/fairway/index.tssrc/lib/admin/data/__tests__/activation-funnel.test.tssrc/lib/admin/data/__tests__/auth.test.tssrc/lib/admin/data/__tests__/incident-feed.test.tssrc/lib/admin/data/__tests__/release-ledger.test.tssrc/lib/admin/data/__tests__/triage.test.tssrc/lib/admin/data/activation-funnel.tssrc/lib/admin/data/activity.tssrc/lib/admin/data/auth.tssrc/lib/admin/data/entity-thread.tssrc/lib/admin/data/errors.tssrc/lib/admin/data/feature-adoption.tssrc/lib/admin/data/golf.tssrc/lib/admin/data/incident-feed.tssrc/lib/admin/data/jobs.tssrc/lib/admin/data/lifting.tssrc/lib/admin/data/overview.tssrc/lib/admin/data/pulse-grid.tssrc/lib/admin/data/release-ledger.tssrc/lib/admin/data/triage.tssrc/lib/admin/data/users.tssrc/lib/admin/github-pr-timeline.ts
| 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'); | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
📐 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.
| 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.
| <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> |
There was a problem hiding this comment.
🎯 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.
| <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.
| {team.playerCount} players · last{' '} | ||
| {team.lastActivity ? <LocalTime iso={team.lastActivity} variant="date" fallback="never" /> : 'never'} |
There was a problem hiding this comment.
🎯 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 thefallback="never"prop.src/app/admin/baseball/page.tsx#L127-L132: Remove thefallback="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.
| {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'} |
| {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.
| <Surface | ||
| padding="sm" | ||
| elevation={card.isLive ? 'shadow' : 'border'} | ||
| className={card.isLive ? 'border-accent-600/40' : undefined} | ||
| > |
There was a problem hiding this comment.
📐 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: replaceborder-accent-600/40.src/app/admin/deploys/_components/ReleaseCard.tsx#L33-L40: replacetext-accent-700.src/app/admin/deploys/_components/ReleaseCard.tsx#L78-L83: replacetext-accent-700.src/app/admin/deploys/_components/ReleaseCard.tsx#L121-L126: replacetext-accent-700.src/app/admin/deploys/_components/ReleaseCardExpand.tsx#L19-L29: replacehover:text-accent-700.src/app/admin/deploys/page.tsx#L188-L210: replace the commit link’stext-accent-700.src/app/admin/deploys/page.tsx#L265-L294: replace the mobile commit link’stext-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-L40src/app/admin/deploys/_components/ReleaseCard.tsx#L78-L83src/app/admin/deploys/_components/ReleaseCard.tsx#L121-L126src/app/admin/deploys/_components/ReleaseCardExpand.tsx#L19-L29src/app/admin/deploys/page.tsx#L188-L210src/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
| const [tab, featureHealth, usersTab, errorsTab] = await Promise.all([ | ||
| fetchGolfTab(), | ||
| fetchFeatureHealth(), | ||
| fetchUsersTab({ sport: 'golf' }), | ||
| fetchErrorsTab({ sport: 'golf', windowHours: 168 }), | ||
| ]); |
There was a problem hiding this comment.
🚀 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.
| 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); |
There was a problem hiding this comment.
🎯 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.
| 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' }; |
There was a problem hiding this comment.
🩺 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/adminRepository: 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 -nRepository: 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 -nRepository: 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 40Repository: 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 -nRepository: 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-42drops theadmin_eventserror; a failed deploy-marker query becomes[], thensrc/lib/admin/data/release-ledger.ts:20-22returnsunconfiguredandsrc/app/admin/deploys/_components/ReleaseLedger.tsx:20-29can show an empty state for a backend failure.src/lib/admin/data/jobs.ts:14-85never checks anyerrorfrom the 18 job queries or the 4 summary queries, so a partial outage renders as zero runs / zero counts insrc/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.
| 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, | ||
| }; |
There was a problem hiding this comment.
🎯 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.
| 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), | ||
| ), |
There was a problem hiding this comment.
🚀 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.
| // `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; |
There was a problem hiding this comment.
🎯 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.
| includeGolf ? admin.from('golf_teams').select('id, name') : emptyRows, | ||
| includeBaseball ? admin.from('baseball_teams').select('id, name') : emptyRows, |
There was a problem hiding this comment.
🗄️ 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.
| 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.
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:
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