Skip to content

fix(admin): Helm Bridge soft failures, incident feed, baseball auth#786

Closed
njrini99-code wants to merge 2 commits into
mainfrom
fix/helm-bridge-observability-baseball-auth
Closed

fix(admin): Helm Bridge soft failures, incident feed, baseball auth#786
njrini99-code wants to merge 2 commits into
mainfrom
fix/helm-bridge-observability-baseball-auth

Conversation

@njrini99-code

@njrini99-code njrini99-code commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Helm Bridge soft-failure logging{ success: false } / { ok: false } / { data: null, error } server-action returns now write to admin_events via observeActionSoftFailure, wired into withBaseballAction, withLiftingAction, and withAdminObserved. Fixes the gap where toast failures during Baseball click-throughs produced zero Bridge rows.
  • Unified incident feed — Overview KPIs, triage queue, and Errors tab share incident-feed.ts (24h window, consistent Sentry lastSeen filtering). Legacy emitters get sport: 'baseball' inference in server-error-logger.
  • Route fixes — Overview command header no longer links to phantom /admin/audit (404); shortcuts now use registered tabs (/admin/auth, etc.) via ADMIN_COMMAND_SHORTCUTS. Player onboarding completion lands on /baseball/player/today directly instead of bouncing through /baseball/dashboard.
  • Baseball login polish — Eliminates triple-loading cascade (auth cache, nav-context TTL, branded BaseballDashboardBootstrap, router.replace on sign-in).
  • Admin platform — Ben + Leah GitHub issue board (workflow select, table, tracker), Work log PR timeline tab (/admin/work), session-activity hardening.

Test plan

  • npm run typecheck
  • Observability + admin-nav route contract tests
  • Sign in to BaseballHelm — confirm single smooth loader into command center
  • Overview → Auth shortcut opens /admin/auth (not 404)
  • Trigger a wrapped action failure — confirm row in /admin/errors with sport: baseball
  • Overview incident count matches Errors tab for 24h window
  • /admin/ben-leah + /admin/work load as expected

…sh baseball auth

Log { success: false } server-action returns to admin_events via observeActionSoftFailure
in withBaseballAction, withLiftingAction, and withAdminObserved so toast failures surface
in Bridge. Unify Overview, triage, and Errors tab on a shared 24h incident feed with
consistent Sentry windowing. Fix baseball login triple-loading cascade with auth/nav
caching and a branded bootstrap loader. Add Ben+Leah issue board workflow, Work log PR
timeline tab, and session-activity hardening.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 4, 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.

@supabase

supabase Bot commented Jul 4, 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 ↗︎.

@vercel

vercel Bot commented Jul 4, 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 4, 2026 6:06pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a GitHub PR work log admin page, a Ben+Leah GitHub issue tracker with workflow updates, a shared incident feed for admin metrics, soft-failure observability for server actions, and baseball auth/session caching and heartbeat changes.

Changes

Admin Work Log

Layer / File(s) Summary
Env config and nav entry
.env.example, src/app/admin/_components/admin-nav.ts, src/app/admin/_components/AdminShell.tsx, src/app/admin/_components/__tests__/admin-nav.test.ts
Adds PR author/limit env vars, registers /admin/work with the new icon and shortcut, and updates nav tests and shell wrapping.
PR parsing and fetch
src/lib/admin/pr-body-parser.ts, src/lib/admin/github-pr-timeline.ts, src/lib/admin/__tests__/pr-body-parser.test.ts
Parses PR bodies into work-area fields and fetches PRs into a work-log snapshot with counts.
Work log page and timeline
src/app/admin/work/page.tsx, src/app/admin/work/WorkTimeline.tsx
Renders work-log states and the month-grouped PR timeline UI with stats and auto-refresh.

Estimated code review effort: 4 (Complex) | ~60 minutes

Ben + Leah Issue Tracker

Layer / File(s) Summary
Track status and workflow labels
src/lib/admin/ben-leah-issue-tracker.ts, src/lib/admin/__tests__/ben-leah-issues.test.ts
Defines tracked-issue types, workflow label helpers, lifecycle derivation, and count utilities with tests.
GitHub config and issue board fetch
src/lib/admin/github-issues-config.ts, src/lib/admin/github-issues-workflow.ts, src/lib/admin/ben-leah-issues.ts, src/lib/admin/github-feedback.ts
Centralizes GitHub config, ensures labels, fetches and normalizes issues, and updates feedback issue creation to use shared label setup.
Workflow update action
src/app/admin/ben-leah/actions.ts
Adds updateBenLeahIssueWorkflow with super-admin/input validation and GitHub label updates.
Board/table/select UI
src/app/admin/ben-leah/BenLeahIssueBoard.tsx, src/app/admin/ben-leah/BenLeahIssueTable.tsx, src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx, src/app/admin/ben-leah/page.tsx
Renders the board, table, workflow selector, and tracker page panel with label guidance and refresh.

Estimated code review effort: 4 (Complex) | ~55 minutes

Incident Feed Unification

Layer / File(s) Summary
Incident feed module
src/lib/admin/data/incident-feed.ts, src/lib/admin/data/__tests__/incident-feed.test.ts
Adds the shared incident feed, filtering/summary helpers, app-event querying, and unit coverage.
Errors tab refactor
src/lib/admin/data/errors.ts, src/app/admin/errors/page.tsx
Refactors errors-tab fetching to use the feed, adds wider-window counts, and updates the page metrics and hinting.
Overview, triage, and admin page metrics
src/lib/admin/data/overview.ts, src/lib/admin/data/triage.ts, src/app/admin/page.tsx
Replaces error-group KPIs with incident-group KPIs, returns triage counts, and updates admin overview and command-header rendering.

Estimated code review effort: 4 (Complex) | ~50 minutes

Server Action Observability

Layer / File(s) Summary
Soft-failure extraction and logging
src/lib/admin/observe-action-result.ts, src/lib/admin/__tests__/observe-action-result.test.ts
Implements soft-failure parsing, classification, throttled logging, and tests.
Wrapper wiring and coverage test
src/lib/admin/observed-action.ts, src/lib/baseball/with-baseball-action.ts, src/lib/lifting/with-lifting-action.ts, src/lib/baseball/__tests__/with-baseball-action-observability.test.ts, src/app/golf/actions/__tests__/coverage-contract.observability.test.ts
Calls soft-failure observation from wrappers and adds the Golf server-action coverage contract test.
Server error logger enrichment
src/lib/server-error-logger.ts, src/lib/__tests__/server-error-logger-bridge.test.ts
Adds trace-context enrichment and updated Sentry/admin persistence fields.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Baseball Auth, Navigation, and Session State

Layer / File(s) Summary
useBaseballAuth caching and user propagation
src/hooks/use-baseball-auth.ts, src/hooks/__tests__/use-baseball-auth.test.tsx
Adds cached auth results, user propagation, cache invalidation, and replace-based redirects.
useBaseballNavContext TTL cache
src/hooks/use-baseball-nav-context.ts
Adds a TTL-backed nav-context cache with warm-cache checks and explicit null-user handling.
Auth-verified shell bootstrap
src/app/baseball/(dashboard)/BaseballFairwayShell.tsx, src/components/baseball/BaseballShellLayout.tsx, src/app/baseball/(dashboard)/layout.tsx, src/components/baseball/BaseballDashboardBootstrap.tsx, src/components/auth/baseball-sign-in-form.tsx
Adds authVerified props, moves shell selection into the dashboard guard, and swaps in the bootstrap loading component and sign-in cache invalidation.
Visible-tab session heartbeat
src/lib/auth/session-idle-shared.ts, src/lib/auth/session-activity.ts, src/components/providers/SessionActivityProvider.tsx, src/test/lib/auth/session-idle-shared.test.ts, src/app/baseball/actions/onboarding.ts, src/app/baseball/actions/__tests__/complete-player-onboarding.test.ts
Adds the visible-tab heartbeat constant, heartbeat-driven activity refresh, admin logout redirect changes, and provider/test updates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: security


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
No Service-Role In Client Bundles ❌ Error FAIL: src/lib/admin/data/incident-feed.ts:2,74 imports @/lib/supabase/admin and calls createAdminClient() outside the allowed admin/api paths. Move that service-role query behind an allowed src/lib/supabase/admin* or src/app/api/**/admin/** helper; keep src/lib/admin/... on non-service clients.
Title check ⚠️ Warning Title uses invalid scope admin, but the PR requires a Conventional Commits scope from the approved list. Change to an allowed scope such as fix(baseball): ... or split the change by area with a valid required scope.
Conventional Commits ❓ Inconclusive HEAD squash subject is conventional, but the PR title isn’t exposed in repo metadata, so the full check can’t be verified. Provide the PR title or PR metadata; the squash commit subject already matches the regex.
✅ Passed checks (9 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Rls Coverage On New Tables ✅ Passed No migration files were changed in the PR diff, so the CREATE TABLE/RLS policy check is not applicable.
Auth Check In Server Actions ✅ Passed PASS: src/app/baseball/actions/onboarding.ts calls supabase.auth.getUser() at 205-206 and 365-368 before any .from() call; src/app/admin/ben-leah/actions.ts has no Supabase .from()/`.rpc(...
Sport-Prefixed Table Names ✅ Passed PASS: onboarding.ts:159-218, use-baseball-auth.ts:83-85, overview.ts:115-129, with-lifting-action.ts:151 all use prefixed tables; no bare coaches/players/teams/rounds/events.
No Destructive Writes ✅ Passed PR writes are idempotent only—onboarding.ts:48-77 uses upsert, github-issues-workflow.ts:28-87 uses POST/PATCH, and server-error-logger.ts:14-46 only inserts.
No Edits To Historical Migrations ✅ Passed git diff shows no supabase/migrations/ files were modified in this PR.
Description check ✅ Passed The description matches the changeset and summarizes the admin, Baseball, incident-feed, and work-log updates.
✨ 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 fix/helm-bridge-observability-baseball-auth
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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

…ing redirect

Replace phantom /admin/audit link with /admin/auth, centralize command-header
shortcuts in admin-nav, and send completed players to /baseball/player/today
instead of an extra /baseball/dashboard dispatcher hop.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai coderabbitai Bot added the security Auth, secrets, RLS, PII, webhooks label Jul 4, 2026
@cursor

cursor Bot commented Jul 4, 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

PR Summary by Qodo

fix(admin): Helm Bridge soft failures, incident feed, baseball auth

🐞 Bug fix ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Adds observeActionSoftFailure to capture { success: false } / { ok: false } / `{ data: null,
 error } server-action returns into admin_events via withBaseballAction, withLiftingAction`,
 and withAdminObserved, closing the gap where toast failures produced no Bridge rows.
• Extracts a shared incident-feed.ts module so Overview KPIs, triage queue, and Errors tab all use
 the same 24h window with consistent Sentry lastSeen filtering; adds IncidentFeedCounts to
 replace scattered inline aggregations.
• Fixes baseball login triple-loading cascade by exporting invalidateAuthCache, adding a TTL to
 the nav-context cache, introducing BaseballDashboardBootstrap as a branded loader, and passing
 authVerified to skip redundant auth checks in child shells.
• Adds /admin/ben-leah issue board with GitHub label-based workflow tracker (status select,
 derived track status, Vercel deploy correlation) and /admin/work PR timeline tab (PR body parser,
 area tagging, month-grouped timeline view).
• Hardens session-activity with a visible-tab heartbeat so reading a dashboard without
 mouse/keyboard does not trip the idle timeout; adds admin-path redirect on session expiry.
• Adds sport tag inference in server-error-logger for legacy emitters that omit context, and
 propagates sport to Sentry scope tags.
Diagram

graph TD
    subgraph Observability["Observability Layer"]
        OAR["observe-action-result.ts"]
        SEL["server-error-logger.ts"]
        AE[("admin_events")]
    end

    subgraph ActionWrappers["Action Wrappers"]
        WBA["withBaseballAction"]
        WLA["withLiftingAction"]
        WAO["withAdminObserved"]
    end

    subgraph IncidentFeed["Incident Feed"]
        IF["incident-feed.ts"]
        TR["triage.ts"]
        ERR["errors.ts"]
        OV["overview.ts"]
    end

    subgraph AdminUI["Admin UI"]
        AP["admin/page.tsx"]
        EP["admin/errors/page.tsx"]
        BLP["admin/ben-leah/page.tsx"]
        WP["admin/work/page.tsx"]
    end

    subgraph BenLeah["Ben+Leah Tracker"]
        BIT["ben-leah-issue-tracker.ts"]
        BIS["ben-leah-issues.ts"]
        GIW["github-issues-workflow.ts"]
        GIC["github-issues-config.ts"]
    end

    subgraph WorkLog["Work Log"]
        GPT["github-pr-timeline.ts"]
        PBP["pr-body-parser.ts"]
    end

    subgraph Auth["Baseball Auth"]
        UBA["use-baseball-auth.ts"]
        UNC["use-baseball-nav-context.ts"]
        BSF["baseball-sign-in-form.tsx"]
        BDB["BaseballDashboardBootstrap.tsx"]
    end

    WBA --> OAR
    WLA --> OAR
    WAO --> OAR
    OAR --> SEL
    SEL --> AE

    IF --> TR
    IF --> ERR
    IF --> OV
    TR --> AP
    ERR --> EP
    OV --> AP

    BIT --> BIS
    GIC --> BIS
    GIC --> GIW
    GIW --> BIS
    BIS --> BLP

    PBP --> GPT
    GIC --> GPT
    GPT --> WP

    BSF --> UBA
    UBA --> UNC
    UBA --> BDB

    subgraph Legend
        direction LR
        _mod["Module"] ~~~ _db[("Database")] ~~~ _ui["UI Page"]
    end
Loading
High-Level Assessment

The PR's approach is well-suited to the problem space. Extracting a shared incident-feed.ts module is the correct way to eliminate the three-way divergence between Overview, triage, and Errors tab — a single source of truth with a pure buildIncidentFeedFromSources function makes the feed unit-testable without mocking Supabase. The soft-failure capture pattern (fire-and-forget observeActionSoftFailure at the wrapper boundary) correctly avoids changing action return types or caller behavior. The auth cache TTL + authVerified prop approach for eliminating the triple-loading cascade is a minimal, targeted fix that avoids a larger auth architecture change. No meaningfully better alternatives were identified.

Files changed (47) +2795 / -216

Enhancement (5) +118 / -10
BaseballDashboardBootstrap.tsxNew branded bootstrap loader for baseball dashboard +20/-0

New branded bootstrap loader for baseball dashboard

• Replaces the generic 'PageLoading' skeleton with a baseball-branded 'AuthPendingDots' loader so the login → command center transition feels cohesive.

src/components/baseball/BaseballDashboardBootstrap.tsx

page.tsxAdd issue board and status label guide to Ben+Leah page +30/-1

Add issue board and status label guide to Ben+Leah page

• Embeds 'BenLeahIssueBoard' in a 'PanelBoundary', adds a GitHub status-label reference panel, and wires 'AutoRefresh' at 60s.

src/app/admin/ben-leah/page.tsx

AdminShell.tsxAdd ScrollText icon for /admin/work; wrap in SessionActivityProvider +5/-1

Add ScrollText icon for /admin/work; wrap in SessionActivityProvider

• Maps the '/admin/work' route to the 'ScrollText' icon and wraps the entire admin shell in 'SessionActivityProvider' for session-activity hardening.

src/app/admin/_components/AdminShell.tsx

session-idle-shared.tsExport SESSION_VISIBLE_HEARTBEAT_MS constant +7/-0

Export SESSION_VISIBLE_HEARTBEAT_MS constant

• Adds 'SESSION_VISIBLE_HEARTBEAT_MS = 60_000' (1 minute) as a shared constant for the visible-tab heartbeat, documented to stay below the idle timeout.

src/lib/auth/session-idle-shared.ts

page.tsxUse shared incident counts; add wider-window hint banner +56/-8

Use shared incident counts; add wider-window hint banner

• Replaces inline aggregations with 'tab.counts' from the shared feed. Adds a contextual warning banner when the current window is empty but the 7-day window has unresolved incidents.

src/app/admin/errors/page.tsx

Bug fix (10) +239 / -61
with-baseball-action.tsWire soft-failure observation into withBaseballAction +2/-0

Wire soft-failure observation into withBaseballAction

• Calls 'observeActionSoftFailure' on the action result after successful execution, so baseball server-action toast failures are captured in Bridge without throwing.

src/lib/baseball/with-baseball-action.ts

with-lifting-action.tsWire soft-failure observation into withLiftingAction +13/-0

Wire soft-failure observation into withLiftingAction

• Adds 'observeActionSoftFailure' call after the lifting action result, tagging with 'sport: 'shared'' and lifting-specific context.

src/lib/lifting/with-lifting-action.ts

observed-action.tsWire soft-failure observation into withAdminObserved +24/-1

Wire soft-failure observation into withAdminObserved

• Captures the action result and calls 'observeActionSoftFailure' with full context (feature, sport, user, round/player/team IDs) before returning it.

src/lib/admin/observed-action.ts

server-error-logger.tsInfer sport/feature for legacy emitters; tag Sentry scope +41/-14

Infer sport/feature for legacy emitters; tag Sentry scope

• Adds 'inferSport' and 'enrichTraceContext' helpers that classify sport from message text and route/action context when 'context.sport' is absent. Propagates 'sport' tag to Sentry scope.

src/lib/server-error-logger.ts

use-baseball-auth.tsAdd auth cache peek, export invalidateAuthCache, use router.replace +46/-14

Add auth cache peek, export invalidateAuthCache, use router.replace

• Initializes 'loading'/'authorized'/'role' state from a warm cache peek to avoid a loading flash. Exports 'invalidateAuthCache' for the sign-in form. Switches unauthorized redirect from 'router.push' to 'router.replace'. Fetches full user record and stores it in auth store.

src/hooks/use-baseball-auth.ts

use-baseball-nav-context.tsAdd TTL to nav-context cache to prevent stale caps after sign-in +42/-9

Add TTL to nav-context cache to prevent stale caps after sign-in

• Introduces 'NAV_CACHE_TTL_MS = 5000' and 'hasWarmNavCache' guard. Adds 'clearNavContextCache' and handles 'userId === null' explicitly to avoid serving stale context to a new session.

src/hooks/use-baseball-nav-context.ts

baseball-sign-in-form.tsxInvalidate auth cache on sign-in; use router.replace +4/-7

Invalidate auth cache on sign-in; use router.replace

• Calls 'invalidateAuthCache()' immediately after successful login, removes the 100ms setTimeout, and switches 'router.push' to 'router.replace' for both returnTo and default redirect paths.

src/components/auth/baseball-sign-in-form.tsx

BaseballFairwayShell.tsxAdd authVerified prop to skip redundant loading gate +8/-2

Add authVerified prop to skip redundant loading gate

• Accepts optional 'authVerified' flag; when true, skips the 'loading || !authorized' check so the parent layout's single auth resolution is not duplicated.

src/app/baseball/(dashboard)/BaseballFairwayShell.tsx

BaseballShellLayout.tsxAdd authVerified prop to BaseballShellLayout +4/-1

Add authVerified prop to BaseballShellLayout

• Mirrors the 'authVerified' pattern from 'BaseballFairwayShell' to skip the full-page skeleton when the parent layout has already verified auth.

src/components/baseball/BaseballShellLayout.tsx

session-activity.tsAdd visible-tab heartbeat to prevent false idle timeouts +55/-13

Add visible-tab heartbeat to prevent false idle timeouts

• Introduces 'startVisibleHeartbeat'/'stopVisibleHeartbeat' that refresh the activity marker every 'SESSION_VISIBLE_HEARTBEAT_MS' while the tab is visible. Adds admin-path redirect on session expiry. Replaces 'handleReshow' with 'syncTabVisibility'.

src/lib/auth/session-activity.ts

Refactor (8) +289 / -141
incident-feed.tsNew shared incident feed module (24h window, Sentry windowing) +121/-0

New shared incident feed module (24h window, Sentry windowing)

• Extracts 'fetchIncidentFeed', 'buildIncidentFeedFromSources', 'filterSentryIssuesByWindow', and 'summarizeIncidentFeed' into a single canonical module. Defines 'IncidentFeedCounts' and 'DEFAULT_INCIDENT_WINDOW_HOURS = 24' used by all three consumers.

src/lib/admin/data/incident-feed.ts

triage.tsRefactor fetchTriageQueue to delegate to incident feed +12/-29

Refactor fetchTriageQueue to delegate to incident feed

• Removes inline Supabase query and Sentry fetch; delegates entirely to 'fetchIncidentFeed'. Returns 'counts' from the shared feed.

src/lib/admin/data/triage.ts

errors.tsRefactor fetchErrorsTab to use shared incident feed +53/-38

Refactor fetchErrorsTab to use shared incident feed

• Replaces inline app-event query and Sentry fetch with 'fetchIncidentFeed'. Adds wider-window (7-day) hint queries for 'widerWindowUnresolved' and 'widerWindowUntagged' counts.

src/lib/admin/data/errors.ts

overview.tsSwitch Overview KPIs to shared incident feed +20/-24

Switch Overview KPIs to shared incident feed

• Replaces separate Sentry + app-events fetches with 'fetchIncidentFeed'. Renames 'eventErrors24h' → 'incidentGroups24h' and uses 'counts.totalGroups' from the feed.

src/lib/admin/data/overview.ts

layout.tsxConsolidate auth + shell rendering in DashboardSessionGuard +12/-12

Consolidate auth + shell rendering in DashboardSessionGuard

• Moves shell selection (Fairway vs legacy) inside 'DashboardSessionGuard' and passes 'authVerified' to skip redundant auth checks in child shells. Simplifies 'DashboardLayout' to a single wrapper.

src/app/baseball/(dashboard)/layout.tsx

github-issues-config.tsExtract shared GitHub Issues API config helpers +25/-0

Extract shared GitHub Issues API config helpers

• Centralizes 'githubIssuesToken', 'githubIssuesRepo', and 'githubIssuesHeaders' into a single server-only module, removing duplication between feedback and tracker paths.

src/lib/admin/github-issues-config.ts

github-feedback.tsRefactor to use shared GitHub config; add initial workflow label +31/-23

Refactor to use shared GitHub config; add initial workflow label

• Delegates token/repo/headers to 'github-issues-config'. Adds 'BEN_LEAH_INITIAL_WORKFLOW_LABEL' ('status:triaged') to new issue payloads and calls 'ensureBenLeahGitHubLabels' before creating issues.

src/lib/admin/github-feedback.ts

page.tsxRename eventErrors24h → incidentGroups24h; use shared counts in triage +15/-15

Rename eventErrors24h → incidentGroups24h; use shared counts in triage

• Updates KPI tile label and value to 'incidentGroups24h'. Triage panel now uses 'counts' from the shared feed instead of inline filter calls.

src/app/admin/page.tsx

Tests (9) +479 / -4
observe-action-result.test.tsTests for observeActionSoftFailure extraction and severity logic +81/-0

Tests for observeActionSoftFailure extraction and severity logic

• Covers envelope extraction for all three failure shapes, expected vs unexpected severity classification, skipSentry on auth noise, and no-op on success.

src/lib/admin/tests/observe-action-result.test.ts

ben-leah-issues.test.tsTests for Ben+Leah issue lifecycle helpers +125/-0

Tests for Ben+Leah issue lifecycle helpers

• Covers 'deriveBenLeahTrackStatus' for all states/labels, workflow label mapping, 'applyWorkflowSelection', 'currentWorkflowSelection', and 'latestProductionReadyAt'.

src/lib/admin/tests/ben-leah-issues.test.ts

incident-feed.test.tsTests for shared incident feed module +66/-0

Tests for shared incident feed module

• Verifies Sentry window filtering and that 'buildIncidentFeedFromSources' produces correct total/app/sentry group counts.

src/lib/admin/data/tests/incident-feed.test.ts

pr-body-parser.test.tsTests for PR body parser section extraction and area inference +57/-0

Tests for PR body parser section extraction and area inference

• Covers full template parsing (problem/fix/area/timeline), title-based area fallback, and 'inferWorkAreaFromTitle' keyword detection.

src/lib/admin/tests/pr-body-parser.test.ts

coverage-contract.observability.test.tsCoverage contract: all Golf server actions must use withAdminObserved +101/-0

Coverage contract: all Golf server actions must use withAdminObserved

• AST-walks all 'use server' files under 'src/app/golf' and asserts every exported function either delegates to a 'withAdminObserved'-wrapped variable, wraps inline, or calls 'logServerException'/'logServerError' directly.

src/app/golf/actions/tests/coverage-contract.observability.test.ts

with-baseball-action-observability.test.tsAdd soft-failure observability test to withBaseballAction suite +28/-0

Add soft-failure observability test to withBaseballAction suite

• Asserts that a '{ success: false }' result is logged via 'logServerError' (not 'logServerException') with correct action/sport/feature context and does not alter the returned value.

src/lib/baseball/tests/with-baseball-action-observability.test.ts

server-error-logger-bridge.test.tsTest sport inference for legacy baseball emitters +11/-0

Test sport inference for legacy baseball emitters

• Adds a test asserting that a message containing 'Baseball' without an explicit 'sport' context is inferred as 'sport: 'baseball'' with 'feature: 'baseball_documents''.

src/lib/tests/server-error-logger-bridge.test.ts

use-baseball-auth.test.tsxUpdate auth hook test to assert router.replace instead of router.push +5/-4

Update auth hook test to assert router.replace instead of router.push

• Switches the tracked mock from 'routerPush' to 'routerReplace' and adds 'setUser' to 'fakeAuthState' to match the updated hook behavior.

src/hooks/tests/use-baseball-auth.test.tsx

session-idle-shared.test.tsAdd test asserting visible heartbeat is shorter than idle timeout +5/-0

Add test asserting visible heartbeat is shorter than idle timeout

• Imports 'SESSION_VISIBLE_HEARTBEAT_MS' and asserts it is less than 'SESSION_IDLE_TIMEOUT_MS'.

src/test/lib/auth/session-idle-shared.test.ts

Documentation (2) +11 / -0
.env.exampleDocument GITHUB_PR_AUTHOR_LOGINS and GITHUB_PR_FETCH_LIMIT env vars +8/-0

Document GITHUB_PR_AUTHOR_LOGINS and GITHUB_PR_FETCH_LIMIT env vars

• Adds a new section for the Work log PR timeline tab with documentation for the two new optional environment variables.

.env.example

SessionActivityProvider.tsxUpdate JSDoc to document visible-tab heartbeat behavior +3/-0

Update JSDoc to document visible-tab heartbeat behavior

• Adds two bullet points to the provider's JSDoc explaining the heartbeat and the hidden-tab-only idle window.

src/components/providers/SessionActivityProvider.tsx

Other (13) +1659 / -0
observe-action-result.tsNew module: capture server-action soft failures to admin_events +105/-0

New module: capture server-action soft failures to admin_events

• Introduces 'observeActionSoftFailure' which detects '{ success: false }', '{ ok: false }', and '{ data: null, error }' result envelopes and logs them via 'logServerError'. Classifies expected auth-noise messages as warnings (skipSentry) and unexpected failures as errors. Throttled via 'emit-throttle' to avoid flooding.

src/lib/admin/observe-action-result.ts

ben-leah-issue-tracker.tsNew pure module: Ben+Leah issue lifecycle types and helpers +198/-0

New pure module: Ben+Leah issue lifecycle types and helpers

• Defines 'BenLeahTrackStatus', 'BenLeahTrackedIssue', workflow label constants, and pure functions: 'deriveBenLeahTrackStatus', 'applyWorkflowSelection', 'currentWorkflowSelection', 'latestProductionReadyAt', 'countBenLeahByStatus'.

src/lib/admin/ben-leah-issue-tracker.ts

ben-leah-issues.tsServer fetcher: fetch and track Ben+Leah GitHub issues +168/-0

Server fetcher: fetch and track Ben+Leah GitHub issues

• Fetches issues labeled 'ben-leah' from GitHub, normalizes them, correlates with Vercel production deploy timing, and returns a 'BenLeahIssueBoardSnapshot' with derived track statuses and counts.

src/lib/admin/ben-leah-issues.ts

github-issues-workflow.tsNew module: GitHub label management for Ben+Leah workflow +90/-0

New module: GitHub label management for Ben+Leah workflow

• Provides 'ensureBenLeahGitHubLabels' (idempotent label creation) and 'setBenLeahIssueWorkflow' (PATCH issue labels via GitHub API) for the workflow select UI.

src/lib/admin/github-issues-workflow.ts

BenLeahIssueBoard.tsxNew server component: Ben+Leah issue board with KPIs and status legend +169/-0

New server component: Ben+Leah issue board with KPIs and status legend

• Renders GitHub integration status pills, stat tiles (open/shipped/pending/total), the issue table, and a per-status legend grid. Handles unconfigured and error states.

src/app/admin/ben-leah/BenLeahIssueBoard.tsx

BenLeahIssueTable.tsxNew client component: scrollable issue table with workflow select +100/-0

New client component: scrollable issue table with workflow select

• Renders a sticky-header table of tracked issues with columns for status pill, workflow select, type, priority, category, updated, and closed timestamps.

src/app/admin/ben-leah/BenLeahIssueTable.tsx

BenLeahIssueWorkflowSelect.tsxNew client component: inline workflow status select for issues +70/-0

New client component: inline workflow status select for issues

• Provides a native select that calls 'updateBenLeahIssueWorkflow' server action and refreshes the router on success; shows inline error on failure.

src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx

actions.tsAdd updateBenLeahIssueWorkflow server action +43/-0

Add updateBenLeahIssueWorkflow server action

• New server action validates super-admin auth, issue number, and workflow selection before calling 'setBenLeahIssueWorkflow'. Returns '{ ok, message }' envelope.

src/app/admin/ben-leah/actions.ts

github-pr-timeline.tsNew server fetcher: GitHub PR timeline for Work log +197/-0

New server fetcher: GitHub PR timeline for Work log

• Fetches PRs via GitHub search API (with list fallback), parses bodies via 'pr-body-parser', infers work area from title, and returns a 'WorkLogSnapshot' with per-area counts.

src/lib/admin/github-pr-timeline.ts

pr-body-parser.tsNew deterministic PR body parser (no AI) +178/-0

New deterministic PR body parser (no AI)

• Extracts Summary, Partner-readable summary, Area, Git Activity Timeline note, and Type of change sections from PR template markdown. Infers 'WorkArea' from body tokens or title keywords.

src/lib/admin/pr-body-parser.ts

WorkTimeline.tsxNew component: month-grouped PR timeline with problem/fix cards +226/-0

New component: month-grouped PR timeline with problem/fix cards

• Renders PRs grouped by month with area/state pills, problem and fix/outcome panels, and a partner-line footer. Shows stat tiles for total/merged/open and top areas.

src/app/admin/work/WorkTimeline.tsx

page.tsxNew /admin/work page: Work log PR timeline +113/-0

New /admin/work page: Work log PR timeline

• Super-admin-gated page that fetches the work log and renders 'WorkTimeline' with sidebar panels explaining how summaries and area tags work. AutoRefresh at 120s.

src/app/admin/work/page.tsx

admin-nav.tsAdd /admin/work to nav with shortcut key W +2/-0

Add /admin/work to nav with shortcut key W

• Registers the Work log entry in 'ADMIN_NAV' under the Operations section with key 'W' and meta 'prs'.

src/app/admin/_components/admin-nav.ts

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 97 rules

Grey Divider


Action required

1. Arbitrary text-[11px] font size 📘 Rule violation ⚙ Maintainability
Description
BenLeahIssueWorkflowSelect uses the Tailwind arbitrary font-size class text-[11px] outside
src/components/ui. This violates the typography token requirement and can lead to inconsistent
text sizing across the app.
Code

src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[67]

+      {error ? <p className="max-w-[12rem] text-[11px] text-fw-danger">{error}</p> : null}
Relevance

⭐⭐⭐ High

Team enforces no arbitrary text-[Npx] via W0 rules and W1.5 sweeps removing them.

PR-#147
PR-#151
PR-#152

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1555254 forbids text-[Npx] classes outside src/components/ui. The added error
message uses text-[11px] in src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx, which is
outside src/components/ui.

Rule 1555254: Disallow arbitrary pixel-based text sizes outside ui components
src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[67-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx` uses an arbitrary Tailwind pixel font size class (`text-[11px]`). The compliance checklist requires using the approved typography token classes outside `src/components/ui`.

## Issue Context
The error message text should use one of: `text-display`, `text-h1`, `text-h2`, `text-h3`, `text-body-lg`, `text-body`, `text-body-sm`, `text-caption`, `text-eyebrow`.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[67-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. 7-day link drops sport 🐞 Bug ≡ Correctness
Description
In ErrorsPage, the “Open 7-day view” link does not preserve the current sport filter (both branches
use the same href), so users can’t open a 7‑day view scoped to the selected sport and the following
“drop the sport filter” link becomes redundant.
Code

src/app/admin/errors/page.tsx[R166-177]

+              <Link
+                href={filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168'}
+                className="text-accent-700 underline"
+              >
+                Open 7-day view
+              </Link>
+              {filters.sport ? (
+                <>
+                  {' '}
+                  or{' '}
+                  <Link href="/admin/errors?window=168" className="text-accent-700 underline">
+                    drop the sport filter
Relevance

⭐⭐ Medium

No historical evidence found about preserving sport/window query params in /admin/errors links.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The link’s conditional expression is identical in both branches, so it always drops the sport filter
even when filters.sport is set.

src/app/admin/errors/page.tsx[146-181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The “Open 7-day view” link on `/admin/errors` should preserve the current filters (especially `sport`) while changing only the `window` to 168h. Right now it always navigates to `/admin/errors?window=168` regardless of whether a sport filter is active.

## Issue Context
This hint renders only when the current window is empty but a wider 7-day window has unresolved/untagged rows. The copy implies two distinct actions: open a 7-day view *with* the sport filter, or explicitly drop the sport filter to include legacy untagged rows.

## Fix Focus Areas
- src/app/admin/errors/page.tsx[146-181]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Workflow overwrites labels 🐞 Bug ☼ Reliability
Description
Ben+Leah workflow updates PATCH the issue’s full label list based on currentLabels provided by the
client, which can drop labels added/removed concurrently in GitHub or via other sessions when the
dropdown is used.
Code

src/lib/admin/github-issues-workflow.ts[R60-82]

+export async function setBenLeahIssueWorkflow(
+  issueNumber: number,
+  currentLabels: string[],
+  selection: BenLeahWorkflowSelection,
+): Promise<void> {
+  const token = githubIssuesToken();
+  if (!token) {
+    throw new Error('GitHub issue token is not configured. Set GITHUB_ISSUES_TOKEN or GITHUB_TOKEN.');
+  }
+
+  await ensureBenLeahGitHubLabels();
+
+  const { owner, repo } = githubIssuesRepo();
+  const labels = applyWorkflowSelection(currentLabels, selection);
+
+  const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, {
+    method: 'PATCH',
+    headers: {
+      ...githubIssuesHeaders(token),
+      'content-type': 'application/json',
+    },
+    body: JSON.stringify({ labels }),
+  });
Relevance

⭐⭐ Medium

No historical evidence found on GitHub label PATCH concurrency/stale-client-label handling in this
repo.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The UI sends issue.labels to the server action, which then PATCHes GitHub with a derived full
label array, making the update dependent on potentially stale client state.

src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[26-44]
src/app/admin/ben-leah/actions.ts[73-111]
src/lib/admin/github-issues-workflow.ts[60-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`setBenLeahIssueWorkflow()` constructs a complete `labels` array from `currentLabels` passed in by the client and PATCHes it to GitHub. This can overwrite concurrent label changes (race/stale UI) and is also unnecessarily trustful of client-provided label state.

## Issue Context
- The client passes `issue.labels` from the rendered snapshot.
- The server action forwards those labels directly.
- The GitHub PATCH updates the issue with the computed label array, overwriting the server’s current labels.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[26-45]
- src/app/admin/ben-leah/actions.ts[73-112]
- src/lib/admin/github-issues-workflow.ts[60-88]

## Suggested implementation direction
- On the server, re-fetch the issue’s current labels from GitHub immediately before applying the workflow change, then apply `applyWorkflowSelection()` to that fresh label set.
 - Alternatively, use GitHub’s labels APIs to add/remove just the workflow labels (avoid full overwrite).
- Consider dropping `currentLabels` from the server action signature entirely to prevent accidental misuse.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Deploy pick not latest 🐞 Bug ≡ Correctness
Description
latestProductionReadyAt() returns the first READY production deployment via Array.find(), but
fetchVercelDeployments() does not sort results; if the API returns deployments out of order,
Ben+Leah issue statuses can be derived from an older production deploy time.
Code

src/lib/admin/ben-leah-issue-tracker.ts[R120-133]

+/** Latest READY production deployment timestamp (epoch ms), if Vercel data is available. */
+export function latestProductionReadyAt(deployments: VercelDeployment[]): {
+  readyAt: number | null;
+  commitSha: string | null;
+} {
+  const prod = deployments.find(
+    (deployment) =>
+      deployment.state === 'READY' &&
+      deployment.target === 'production' &&
+      deployment.ready != null,
+  );
+  if (!prod?.ready) return { readyAt: null, commitSha: null };
+  return { readyAt: prod.ready, commitSha: prod.commitSha };
+}
Relevance

⭐ Low

Repo codified behavior: latestProductionReadyAt “returns the first READY production deployment”
(ordering assumed).

PR-#768

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tracker chooses the first matching production deploy, while the Vercel fetcher returns
deployments without enforcing any ordering, so “first” is not guaranteed to mean “latest.”

src/lib/admin/ben-leah-issue-tracker.ts[120-133]
src/lib/admin/vercel-api.ts[82-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`latestProductionReadyAt()` should return the most recent READY production deployment, not the first match in the array. Because `fetchVercelDeployments()` preserves API order without sorting, relying on `Array.find()` can pick a stale deploy and misclassify issues as `fixed_pending_deploy` vs `in_production`.

## Issue Context
The returned `productionReadyAt` is used to decide whether a closed GitHub issue is already shipped (production deploy after close time) or still pending deploy.

## Fix Focus Areas
- src/lib/admin/ben-leah-issue-tracker.ts[120-133]
- src/lib/admin/vercel-api.ts[82-112]

## Suggested implementation direction
- Filter to READY + production + ready != null, then pick the maximum `ready` timestamp (and associated commitSha).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

</option>
))}
</NativeSelect>
{error ? <p className="max-w-[12rem] text-[11px] text-fw-danger">{error}</p> : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Arbitrary text-[11px] font size 📘 Rule violation ⚙ Maintainability

BenLeahIssueWorkflowSelect uses the Tailwind arbitrary font-size class text-[11px] outside
src/components/ui. This violates the typography token requirement and can lead to inconsistent
text sizing across the app.
Agent Prompt
## Issue description
`src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx` uses an arbitrary Tailwind pixel font size class (`text-[11px]`). The compliance checklist requires using the approved typography token classes outside `src/components/ui`.

## Issue Context
The error message text should use one of: `text-display`, `text-h1`, `text-h2`, `text-h3`, `text-body-lg`, `text-body`, `text-body-sm`, `text-caption`, `text-eyebrow`.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[67-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +166 to +177
<Link
href={filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168'}
className="text-accent-700 underline"
>
Open 7-day view
</Link>
{filters.sport ? (
<>
{' '}
or{' '}
<Link href="/admin/errors?window=168" className="text-accent-700 underline">
drop the sport filter

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. 7-day link drops sport 🐞 Bug ≡ Correctness

In ErrorsPage, the “Open 7-day view” link does not preserve the current sport filter (both branches
use the same href), so users can’t open a 7‑day view scoped to the selected sport and the following
“drop the sport filter” link becomes redundant.
Agent Prompt
## Issue description
The “Open 7-day view” link on `/admin/errors` should preserve the current filters (especially `sport`) while changing only the `window` to 168h. Right now it always navigates to `/admin/errors?window=168` regardless of whether a sport filter is active.

## Issue Context
This hint renders only when the current window is empty but a wider 7-day window has unresolved/untagged rows. The copy implies two distinct actions: open a 7-day view *with* the sport filter, or explicitly drop the sport filter to include legacy untagged rows.

## Fix Focus Areas
- src/app/admin/errors/page.tsx[146-181]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +60 to +82
export async function setBenLeahIssueWorkflow(
issueNumber: number,
currentLabels: string[],
selection: BenLeahWorkflowSelection,
): Promise<void> {
const token = githubIssuesToken();
if (!token) {
throw new Error('GitHub issue token is not configured. Set GITHUB_ISSUES_TOKEN or GITHUB_TOKEN.');
}

await ensureBenLeahGitHubLabels();

const { owner, repo } = githubIssuesRepo();
const labels = applyWorkflowSelection(currentLabels, selection);

const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, {
method: 'PATCH',
headers: {
...githubIssuesHeaders(token),
'content-type': 'application/json',
},
body: JSON.stringify({ labels }),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Workflow overwrites labels 🐞 Bug ☼ Reliability

Ben+Leah workflow updates PATCH the issue’s full label list based on currentLabels provided by the
client, which can drop labels added/removed concurrently in GitHub or via other sessions when the
dropdown is used.
Agent Prompt
## Issue description
`setBenLeahIssueWorkflow()` constructs a complete `labels` array from `currentLabels` passed in by the client and PATCHes it to GitHub. This can overwrite concurrent label changes (race/stale UI) and is also unnecessarily trustful of client-provided label state.

## Issue Context
- The client passes `issue.labels` from the rendered snapshot.
- The server action forwards those labels directly.
- The GitHub PATCH updates the issue with the computed label array, overwriting the server’s current labels.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx[26-45]
- src/app/admin/ben-leah/actions.ts[73-112]
- src/lib/admin/github-issues-workflow.ts[60-88]

## Suggested implementation direction
- On the server, re-fetch the issue’s current labels from GitHub immediately before applying the workflow change, then apply `applyWorkflowSelection()` to that fresh label set.
  - Alternatively, use GitHub’s labels APIs to add/remove just the workflow labels (avoid full overwrite).
- Consider dropping `currentLabels` from the server action signature entirely to prevent accidental misuse.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +75 to +82
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, {
method: 'PATCH',
headers: {
...githubIssuesHeaders(token),
'content-type': 'application/json',
},
body: JSON.stringify({ labels }),
});
};

function stripHtmlComments(text: string): string {
return text.replace(/<!--[\s\S]*?-->/g, '');

if (!shouldPersistAdminTables()) {
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, context.action ?? '');
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, enriched.action ?? '');

if (!shouldPersistAdminTables()) {
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, context.action ?? '');
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, enriched.action ?? '');

if (!shouldPersistAdminTables()) {
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, context.action ?? '');
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, enriched.action ?? '');

if (!shouldPersistAdminTables()) {
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, context.action ?? '');
console.error(`[ServerErrorLogger] (${severity}, not persisted off-prod)`, message, enriched.action ?? '');
await writeAdminTables(message, normalizedError, enriched, severity);
} catch {
console.error('[ServerErrorLogger] Failed to persist trace:', message, context);
console.error('[ServerErrorLogger] Failed to persist trace:', message, enriched);
await writeAdminTables(message, normalizedError, enriched, severity);
} catch {
console.error('[ServerErrorLogger] Failed to persist trace:', message, context);
console.error('[ServerErrorLogger] Failed to persist trace:', message, enriched);
return Promise.resolve(cachedContext);
}

if (userId === null) {

@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: 18

🤖 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/ben-leah/BenLeahIssueWorkflowSelect.tsx`:
- Line 67: The error message paragraph in BenLeahIssueWorkflowSelect uses an
arbitrary Tailwind font size outside the UI components area. Update the
conditional error render in BenLeahIssueWorkflowSelect to replace the
text-[11px] class with the appropriate canonical text token from the design
system, keeping the existing max-width and danger color styling intact.

In `@src/app/admin/errors/page.tsx`:
- Around line 166-171: The “Open 7-day view” link in the admin errors page is
using a dead ternary and always navigates to the same URL, which drops the
active sport filter. Update the Link in the page component so the href built for
the 7-day view preserves filters.sport when it is present, and leave the
separate “drop the sport filter” link as the only place that removes it. Use the
existing filters logic in the admin errors page to construct the correct query
string.

In `@src/app/admin/page.tsx`:
- Line 342: The Command Center overview is fetching the incident feed twice in
the same render path, once through TriagePanel via fetchTriageQueue and again
through BannerAndKpis via fetchOverviewSnapshot. Hoist a single
fetchIncidentFeed result in src/app/admin/page.tsx (or reuse the existing
prefetched.sentry support in fetchIncidentFeed) and thread the shared Sentry
payload into both panels so both use the same 24h feed instead of issuing
duplicate reads.

In `@src/hooks/use-baseball-nav-context.ts`:
- Around line 64-71: The baseball nav cache is keyed only by userId, so team
switches can reuse stale nav state until TTL expires. Update
use-baseball-nav-context’s caching logic in hasWarmNavCache and the related
cache setters/resetters to include activeTeamId in the cache key, or explicitly
invalidate the cache whenever active_baseball_team changes. Make sure the cache
aligns with getBaseballNavContext, which derives capabilities and programType
from the active team.

In `@src/lib/admin/__tests__/ben-leah-issues.test.ts`:
- Around line 30-70: Add test coverage in deriveBenLeahTrackStatus for the
closed-issue fallback when productionReadyAt is null so it returns fixed, and
add a case that exercises latestProductionReadyAt with multiple READY production
deployments listed out of order. Use the existing issue() helper and
deriveBenLeahTrackStatus assertions in ben-leah-issues.test.ts to cover the
default fixed path and to verify the tracker logic does not depend on array
order, matching the behavior in ben-leah-issue-tracker.ts.

In `@src/lib/admin/ben-leah-issue-tracker.ts`:
- Around line 121-133: The latestProductionReadyAt helper is order-dependent
because it uses deployments.find(), so it can return an older READY production
deployment instead of the newest one. Update latestProductionReadyAt to scan all
eligible VercelDeployment entries and choose the deployment with the greatest
ready timestamp, keeping the existing readyAt/commitSha return shape. Make sure
the selection logic is independent of the array order produced by
fetchVercelDeployments().

In `@src/lib/admin/ben-leah-issues.ts`:
- Around line 141-147: The board fetch path in ben-leah-issues should not
trigger label bootstrapping on every read. Remove the call to
ensureBenLeahGitHubLabels from the board-loading flow in ben-leah-issues and
keep label creation confined to the write paths handled by github-feedback and
github-issues-workflow. If labels still need initialization, move that
responsibility to the label-writing actions rather than the read-only fetch
path.

In `@src/lib/admin/data/incident-feed.ts`:
- Around line 92-94: The `incidentFeed` data fetch is swallowing Supabase
`admin_events` query failures by destructuring only `data` and returning an
empty array, which makes errors look like a valid empty feed. Update the
`incident-feed.ts` fetch path to preserve the query result’s `error` and either
throw on failure or return an explicit app-events status flag, then make sure
callers like `overview.ts` can distinguish failed app-events queries from truly
empty results.

In `@src/lib/admin/github-issues-workflow.ts`:
- Around line 60-88: The label update flow is using a stale client snapshot, so
concurrent GitHub label changes can be overwritten. Update
setBenLeahIssueWorkflow() to fetch the issue’s current labels server-side
immediately before calling the PATCH request, then pass those live labels into
applyWorkflowSelection() instead of the issue.labels value from
BenLeahIssueWorkflowSelect. Keep the existing GitHub API request and error
handling in github-issues-workflow.ts, but ensure the workflow selection is
merged against fresh labels right before writing.
- Around line 28-38: The GitHub API fetches in ensureBenLeahGitHubLabels and
setBenLeahIssueWorkflow can hang indefinitely because they do not use an
AbortSignal. Update both fetch calls in github-issues-workflow.ts to include a
timeout-backed signal, ideally via a shared helper or
AbortSignal.timeout(10_000), so the admin workflow fails fast if api.github.com
stalls.

In `@src/lib/admin/github-pr-timeline.ts`:
- Around line 92-106: The GitHub issue search query in github-pr-timeline’s
author filter is being built with literal plus signs, which causes the author
constraint to be serialized incorrectly. Update the logic in the
authorQuery/searchParams construction so the q value uses space-separated
qualifiers like author:foo author:bar instead of + separators, and keep the rest
of the GitHub search request in github-pr-timeline unchanged.

In `@src/lib/admin/observe-action-result.ts`:
- Around line 25-59: Soft-failure messages are being persisted verbatim into
admin events, so redact or normalize user-derived content before saving. Update
extractActionSoftFailure and the observeActionResult persistence path so
failure.message and the `[${context.action}] ...` wrapper only store approved
user-facing text or a sanitized version with identifiers removed. Use the
existing symbols extractActionSoftFailure and observeActionResult to keep the
redaction centralized for all callers that pass through these action result
envelopes.

In `@src/lib/admin/observed-action.ts`:
- Around line 77-100: The success path in withAdminObserved is doing unnecessary
work on every call by resolving the observed user and building extra context
before confirming there is a soft failure to record. Update the flow around fn,
observeActionSoftFailure, and resolveObservedUser so you first perform the cheap
extractActionSoftFailure check on result and only then call
resolveObservedUser() and opts.contextFrom?.(args) when there is an actual soft
failure to log. Keep the existing context fields and handled flag, but gate the
expensive auth/network round-trip behind that condition.

In `@src/lib/admin/pr-body-parser.ts`:
- Around line 160-178: In pr-body-parser.ts, the parsing logic in the
summary/partnerSummary handling can produce duplicate Problem and Fix text when
only Summary is present. Update the return flow around splitPartnerSummary,
problem, and fix so fix does not fall back to summary when it would equal
problem; instead, leave it null or emit a distinct placeholder that TimelineCard
in WorkTimeline.tsx can display without duplicating the Problem panel. Ensure
the behavior still prefers partnerParts.fix and timelineNote, and only uses
summary as a fallback when it produces a meaningfully different Fix value.

In `@src/lib/baseball/__tests__/with-baseball-action-observability.test.ts`:
- Around line 158-183: Add an integration test in withBaseballAction that
exercises the expected/auth-noise soft-failure path, not just the unexpected
error branch. Use withBaseballAction and the existing Helm Bridge mock
assertions to verify that a soft failure like "Not authenticated" is logged via
logServerError with severity "warning" and skipSentry set to true, while still
returning { success: false, error: ... } and preserving the same action metadata
shape checked in the current test.

In `@src/lib/lifting/with-lifting-action.ts`:
- Around line 181-192: Add a dedicated observability test for
with-lifting-action.ts to cover the new observeActionSoftFailure wiring,
mirroring the existing with-baseball-action-observability.test.ts pattern.
Create a with-lifting-action-observability.test.ts that exercises the
withLiftingAction wrapper’s soft-failure path and asserts
observeActionSoftFailure is called with the expected action,
featureArea/feature, source, sport, userId/userEmail, teamId, handled flag, and
tags. Use the unique symbols withLiftingAction and observeActionSoftFailure to
locate the wrapper and keep the test aligned with the baseball cohort’s
coverage.
- Around line 181-192: The soft-failure event in withLiftingAction is persisting
the wrong sport grouping key, so it is being stored as shared while only the
Sentry tag says lifting. Update the observeActionSoftFailure call in
withLiftingAction to set the top-level sport field to lifting so
admin_events.sport matches the incident-feed grouping used by
server-error-logger, incident-feed, and triage. Keep the existing tags.sport
metadata if needed, but make sure the persisted sport value and the
lifting-specific grouping are aligned.

In `@src/lib/server-error-logger.ts`:
- Around line 151-172: The issue is that inferSport() and enrichTraceContext()
classify the same error using different probes, which can produce conflicting
sport and feature values. Update server-error-logger’s inferSport() and
enrichTraceContext() so classification happens once from a single shared
probe/result, then use that same classification to populate both sport and
feature. Keep the logic centered around classifyTraceSurface() and
enrichTraceContext() to ensure admin_events.sport and admin_events.feature
always stay aligned.
🪄 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: c3b673bd-2613-4c6d-b993-9279622e7fbe

📥 Commits

Reviewing files that changed from the base of the PR and between afbbd27 and 328af2f.

📒 Files selected for processing (48)
  • .env.example
  • src/app/admin/_components/AdminShell.tsx
  • src/app/admin/_components/__tests__/admin-nav.test.ts
  • src/app/admin/_components/admin-nav.ts
  • src/app/admin/ben-leah/BenLeahIssueBoard.tsx
  • src/app/admin/ben-leah/BenLeahIssueTable.tsx
  • src/app/admin/ben-leah/BenLeahIssueWorkflowSelect.tsx
  • src/app/admin/ben-leah/actions.ts
  • src/app/admin/ben-leah/page.tsx
  • src/app/admin/errors/page.tsx
  • src/app/admin/page.tsx
  • src/app/admin/work/WorkTimeline.tsx
  • src/app/admin/work/page.tsx
  • src/app/baseball/(dashboard)/BaseballFairwayShell.tsx
  • src/app/baseball/(dashboard)/layout.tsx
  • src/app/golf/actions/__tests__/coverage-contract.observability.test.ts
  • src/components/auth/baseball-sign-in-form.tsx
  • src/components/baseball/BaseballDashboardBootstrap.tsx
  • src/components/baseball/BaseballShellLayout.tsx
  • src/components/providers/SessionActivityProvider.tsx
  • src/hooks/__tests__/use-baseball-auth.test.tsx
  • src/hooks/use-baseball-auth.ts
  • src/hooks/use-baseball-nav-context.ts
  • src/lib/__tests__/server-error-logger-bridge.test.ts
  • src/lib/admin/__tests__/ben-leah-issues.test.ts
  • src/lib/admin/__tests__/observe-action-result.test.ts
  • src/lib/admin/__tests__/pr-body-parser.test.ts
  • src/lib/admin/ben-leah-issue-tracker.ts
  • src/lib/admin/ben-leah-issues.ts
  • src/lib/admin/data/__tests__/incident-feed.test.ts
  • src/lib/admin/data/errors.ts
  • src/lib/admin/data/incident-feed.ts
  • src/lib/admin/data/overview.ts
  • src/lib/admin/data/triage.ts
  • src/lib/admin/github-feedback.ts
  • src/lib/admin/github-issues-config.ts
  • src/lib/admin/github-issues-workflow.ts
  • src/lib/admin/github-pr-timeline.ts
  • src/lib/admin/observe-action-result.ts
  • src/lib/admin/observed-action.ts
  • src/lib/admin/pr-body-parser.ts
  • src/lib/auth/session-activity.ts
  • src/lib/auth/session-idle-shared.ts
  • src/lib/baseball/__tests__/with-baseball-action-observability.test.ts
  • src/lib/baseball/with-baseball-action.ts
  • src/lib/lifting/with-lifting-action.ts
  • src/lib/server-error-logger.ts
  • src/test/lib/auth/session-idle-shared.test.ts

</option>
))}
</NativeSelect>
{error ? <p className="max-w-[12rem] text-[11px] text-fw-danger">{error}</p> : null}

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 | 🟡 Minor | ⚡ Quick win

Arbitrary text-[11px] outside src/components/ui.

Path instructions require the canonical text scale (text-caption, text-body-sm, etc.) instead of arbitrary pixel sizes for files outside src/components/ui.

🎨 Use a canonical text token
-      {error ? <p className="max-w-[12rem] text-[11px] text-fw-danger">{error}</p> : null}
+      {error ? <p className="max-w-[12rem] text-caption text-fw-danger">{error}</p> : null}

As per path instructions, "Do not use arbitrary text-[Npx] sizes outside src/components/ui; use the canonical text scale tokens instead."

📝 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
{error ? <p className="max-w-[12rem] text-[11px] text-fw-danger">{error}</p> : null}
{error ? <p className="max-w-[12rem] text-caption text-fw-danger">{error}</p> : null}
🤖 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/ben-leah/BenLeahIssueWorkflowSelect.tsx` at line 67, The error
message paragraph in BenLeahIssueWorkflowSelect uses an arbitrary Tailwind font
size outside the UI components area. Update the conditional error render in
BenLeahIssueWorkflowSelect to replace the text-[11px] class with the appropriate
canonical text token from the design system, keeping the existing max-width and
danger color styling intact.

Source: Path instructions

Comment on lines +166 to +171
<Link
href={filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168'}
className="text-accent-700 underline"
>
Open 7-day view
</Link>

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

Dead ternary drops the active sport filter on "Open 7-day view." src/app/admin/errors/page.tsx:167 — both branches of filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168' are byte-identical, so the conditional is a no-op and the sport filter is discarded whenever it's set. Since the separate link at line 176 already exists to "drop the sport filter," the primary "Open 7-day view" link should preserve it.

🐛 Preserve sport on the primary link
               <Link
-                href={filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168'}
+                href={filters.sport ? `/admin/errors?window=168&sport=${filters.sport}` : '/admin/errors?window=168'}
                 className="text-accent-700 underline"
               >
                 Open 7-day view
               </Link>
📝 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
<Link
href={filters.sport ? '/admin/errors?window=168' : '/admin/errors?window=168'}
className="text-accent-700 underline"
>
Open 7-day view
</Link>
<Link
href={filters.sport ? `/admin/errors?window=168&sport=${filters.sport}` : '/admin/errors?window=168'}
className="text-accent-700 underline"
>
Open 7-day view
</Link>
🤖 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/errors/page.tsx` around lines 166 - 171, The “Open 7-day view”
link in the admin errors page is using a dead ternary and always navigates to
the same URL, which drops the active sport filter. Update the Link in the page
component so the href built for the 7-day view preserves filters.sport when it
is present, and leave the separate “drop the sport filter” link as the only
place that removes it. Use the existing filters logic in the admin errors page
to construct the correct query string.

Comment thread src/app/admin/page.tsx

async function TriagePanel() {
const { items, sentry } = await fetchTriageQueue();
const { items, sentry, counts } = await fetchTriageQueue();

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 | 🔵 Trivial | ⚖️ Poor tradeoff

Overview page fetches the incident feed twice per render. TriagePanel calls fetchTriageQueue() (src/app/admin/page.tsx:342) which runs fetchIncidentFeed, and BannerAndKpis (line 85) runs fetchOverviewSnapshot which runs fetchIncidentFeed again — so every Command Center load hits the Sentry read API and the admin_events query twice with identical 24h filters. fetchIncidentFeed already exposes a prefetched: { sentry } param precisely to dedupe this.

This is a reasonable trade for independent per-panel streaming/degradation, but given Sentry API rate limits it's worth hoisting one feed fetch and threading it (or at least the Sentry result) into both panels.

🤖 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/page.tsx` at line 342, The Command Center overview is fetching
the incident feed twice in the same render path, once through TriagePanel via
fetchTriageQueue and again through BannerAndKpis via fetchOverviewSnapshot.
Hoist a single fetchIncidentFeed result in src/app/admin/page.tsx (or reuse the
existing prefetched.sentry support in fetchIncidentFeed) and thread the shared
Sentry payload into both panels so both use the same 24h feed instead of issuing
duplicate reads.

Comment on lines +64 to +71
function hasWarmNavCache(userId: string | null): boolean {
return (
userId !== null &&
userId === cacheUserId &&
cachedContext !== null &&
Date.now() - cacheResolvedAt < NAV_CACHE_TTL_MS
);
}

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
# Look for team-switch invalidation of the nav cache
rg -nP 'clearNavContextCache|setActiveTeam|switchTeam|activeTeamId' src/lib/baseball src/hooks src/stores -C3

Repository: njrini99-code/helmv3

Length of output: 19600


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== use-baseball-nav-context.ts =="
cat -n src/hooks/use-baseball-nav-context.ts | sed -n '1,220p'

echo
echo "== clearNavContextCache references =="
rg -n "clearNavContextCache" src -C 2

echo
echo "== active team / switcher references =="
rg -n "activeTeamId|setActiveTeam|switchTeam|team switch|switch team|select team|current team" src/app src/components src/hooks src/lib/baseball src/stores -C 2

Repository: njrini99-code/helmv3

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the hook around the cache and effect logic with line numbers.
sed -n '1,220p' src/hooks/use-baseball-nav-context.ts | nl -ba

echo
# Find the active team resolver to see what actually changes the context.
sed -n '1,220p' src/lib/baseball/active-context.ts | nl -ba

echo
# Locate any team-switch UI or action that might clear the nav cache.
rg -n "clearNavContextCache|activeTeamId|setActiveTeam|switchTeam|teamId" src/app src/components src/hooks src/lib/baseball src/stores -C 2

Repository: njrini99-code/helmv3

Length of output: 198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n src/hooks/use-baseball-nav-context.ts | sed -n '1,220p'

Repository: njrini99-code/helmv3

Length of output: 6607


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== useBaseballNavContext usages =="
rg -n "useBaseballNavContext" src -C 2

echo
echo "== baseball active-team switch / cookie refs =="
rg -n "active_baseball_team|setActiveBaseball|switchTeam|team switch|fellBackFromStale|clearNavContextCache" src/app/baseball src/components/baseball src/lib/baseball src/hooks -C 2

echo
echo "== baseball team switcher / active-context action files =="
fd -a 'team-switcher*' src/app/baseball src/components/baseball src/lib/baseball src/hooks 2>/dev/null || true
fd -a '*active*team*' src/app/baseball src/components/baseball src/lib/baseball src/hooks 2>/dev/null || true

Repository: njrini99-code/helmv3

Length of output: 12298


Key the baseball nav cache by active team. src/hooks/use-baseball-nav-context.ts:64-71 caches by userId only, and src/hooks/use-baseball-nav-context.ts:122-151 never invalidates on team changes. getBaseballNavContext() reads activeTeamId when resolving capabilities and programType (src/lib/baseball/nav-context.ts:118-127), so a multi-team coach can keep the previous team's gated nav until the 5s TTL expires. Add activeTeamId to the cache key or clear the cache when the team switcher updates active_baseball_team.

🤖 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/hooks/use-baseball-nav-context.ts` around lines 64 - 71, The baseball nav
cache is keyed only by userId, so team switches can reuse stale nav state until
TTL expires. Update use-baseball-nav-context’s caching logic in hasWarmNavCache
and the related cache setters/resetters to include activeTeamId in the cache
key, or explicitly invalidate the cache whenever active_baseball_team changes.
Make sure the cache aligns with getBaseballNavContext, which derives
capabilities and programType from the active team.

Comment on lines +30 to +70
describe('deriveBenLeahTrackStatus', () => {
it('marks open issues without workflow labels as open', () => {
expect(deriveBenLeahTrackStatus(issue(), null)).toBe('open');
});

it('marks status:in-progress as in progress while open', () => {
expect(
deriveBenLeahTrackStatus(issue({ labels: ['ben-leah', 'status:in-progress'] }), null),
).toBe('in_progress');
});

it('marks status:wontfix regardless of state', () => {
expect(
deriveBenLeahTrackStatus(issue({ state: 'closed', labels: ['status:wontfix'] }), null),
).toBe('wont_fix');
});

it('marks closed issues shipped when production deploy is newer than close', () => {
const closed = issue({
state: 'closed',
closed_at: '2026-07-04T12:00:00.000Z',
});
const prodReady = Date.parse('2026-07-04T13:00:00.000Z');
expect(deriveBenLeahTrackStatus(closed, prodReady)).toBe('in_production');
});

it('marks closed issues pending deploy when production is older than close', () => {
const closed = issue({
state: 'closed',
closed_at: '2026-07-04T14:00:00.000Z',
});
const prodReady = Date.parse('2026-07-04T12:00:00.000Z');
expect(deriveBenLeahTrackStatus(closed, prodReady)).toBe('fixed_pending_deploy');
});

it('honors explicit status:in-production label', () => {
expect(
deriveBenLeahTrackStatus(issue({ labels: ['status:in-production'] }), null),
).toBe('in_production');
});
});

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 | 🔵 Trivial | ⚡ Quick win

Add coverage for the fixed fallback and multi-deployment ordering.

No test covers a closed issue with productionReadyAt === null (defaults to 'fixed') — the common case when Vercel isn't configured. Also no test for latestProductionReadyAt with multiple READY production deployments out of order, which would have caught the .find() ordering issue flagged in ben-leah-issue-tracker.ts.

✅ Suggested additional cases
   it('honors explicit status:in-production label', () => {
     expect(
       deriveBenLeahTrackStatus(issue({ labels: ['status:in-production'] }), null),
     ).toBe('in_production');
   });
+
+  it('falls back to fixed when closed with no production deploy data', () => {
+    const closed = issue({ state: 'closed', closed_at: '2026-07-04T14:00:00.000Z' });
+    expect(deriveBenLeahTrackStatus(closed, null)).toBe('fixed');
+  });
 });

Also applies to: 92-125

🤖 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/__tests__/ben-leah-issues.test.ts` around lines 30 - 70, Add
test coverage in deriveBenLeahTrackStatus for the closed-issue fallback when
productionReadyAt is null so it returns fixed, and add a case that exercises
latestProductionReadyAt with multiple READY production deployments listed out of
order. Use the existing issue() helper and deriveBenLeahTrackStatus assertions
in ben-leah-issues.test.ts to cover the default fixed path and to verify the
tracker logic does not depend on array order, matching the behavior in
ben-leah-issue-tracker.ts.

Comment on lines 77 to +100
try {
return await fn(...args);
const result = await fn(...args);
const observedUser = await resolveObservedUser();
let extraContext: ObservedActionContext = {};
try {
extraContext = opts.contextFrom?.(args) ?? {};
} catch {
extraContext = {};
}
observeActionSoftFailure(result, {
action: name,
source: 'server_action',
feature: opts.feature ?? null,
featureArea: opts.featureArea ?? opts.feature ?? null,
sport: opts.sport,
userId: observedUser.userId,
userEmail: observedUser.userEmail,
roundId: extraContext.roundId ?? null,
playerId: extraContext.playerId ?? null,
teamId: extraContext.teamId ?? null,
route: extraContext.route ?? null,
handled: true,
});
return 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.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Unconditional auth round-trip added to every successful call — gate it behind an actual soft-failure detection.

resolveObservedUser() (a fresh supabase.auth.getUser() network call) and opts.contextFrom?.(args) now run on every successful invocation of any withAdminObserved-wrapped action, before checking whether result is even a soft failure. Previously this path was a plain return await fn(...args). Since observeActionSoftFailure internally bails out immediately via extractActionSoftFailure(result) for the vast majority of normal successes (line 79 of observe-action-result.ts), this work is thrown away most of the time while still paying the auth-server latency on the hot path for every admin action.

Hoist the cheap synchronous extractActionSoftFailure check before doing the expensive/async identity resolution:

⚡ Proposed fix — only resolve identity when there's actually something to log
+import { extractActionSoftFailure, observeActionSoftFailure } from '`@/lib/admin/observe-action-result`';
-import { observeActionSoftFailure } from '`@/lib/admin/observe-action-result`';
...
       const result = await fn(...args);
-      const observedUser = await resolveObservedUser();
-      let extraContext: ObservedActionContext = {};
-      try {
-        extraContext = opts.contextFrom?.(args) ?? {};
-      } catch {
-        extraContext = {};
-      }
-      observeActionSoftFailure(result, {
-        action: name,
-        source: 'server_action',
-        feature: opts.feature ?? null,
-        featureArea: opts.featureArea ?? opts.feature ?? null,
-        sport: opts.sport,
-        userId: observedUser.userId,
-        userEmail: observedUser.userEmail,
-        roundId: extraContext.roundId ?? null,
-        playerId: extraContext.playerId ?? null,
-        teamId: extraContext.teamId ?? null,
-        route: extraContext.route ?? null,
-        handled: true,
-      });
+      if (extractActionSoftFailure(result)) {
+        const observedUser = await resolveObservedUser();
+        let extraContext: ObservedActionContext = {};
+        try {
+          extraContext = opts.contextFrom?.(args) ?? {};
+        } catch {
+          extraContext = {};
+        }
+        observeActionSoftFailure(result, {
+          action: name,
+          source: 'server_action',
+          feature: opts.feature ?? null,
+          featureArea: opts.featureArea ?? opts.feature ?? null,
+          sport: opts.sport,
+          userId: observedUser.userId,
+          userEmail: observedUser.userEmail,
+          roundId: extraContext.roundId ?? null,
+          playerId: extraContext.playerId ?? null,
+          teamId: extraContext.teamId ?? null,
+          route: extraContext.route ?? null,
+          handled: true,
+        });
+      }
       return result;
📝 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
try {
return await fn(...args);
const result = await fn(...args);
const observedUser = await resolveObservedUser();
let extraContext: ObservedActionContext = {};
try {
extraContext = opts.contextFrom?.(args) ?? {};
} catch {
extraContext = {};
}
observeActionSoftFailure(result, {
action: name,
source: 'server_action',
feature: opts.feature ?? null,
featureArea: opts.featureArea ?? opts.feature ?? null,
sport: opts.sport,
userId: observedUser.userId,
userEmail: observedUser.userEmail,
roundId: extraContext.roundId ?? null,
playerId: extraContext.playerId ?? null,
teamId: extraContext.teamId ?? null,
route: extraContext.route ?? null,
handled: true,
});
return result;
import { extractActionSoftFailure, observeActionSoftFailure } from '`@/lib/admin/observe-action-result`';
...
try {
const result = await fn(...args);
if (extractActionSoftFailure(result)) {
const observedUser = await resolveObservedUser();
let extraContext: ObservedActionContext = {};
try {
extraContext = opts.contextFrom?.(args) ?? {};
} catch {
extraContext = {};
}
observeActionSoftFailure(result, {
action: name,
source: 'server_action',
feature: opts.feature ?? null,
featureArea: opts.featureArea ?? opts.feature ?? null,
sport: opts.sport,
userId: observedUser.userId,
userEmail: observedUser.userEmail,
roundId: extraContext.roundId ?? null,
playerId: extraContext.playerId ?? null,
teamId: extraContext.teamId ?? null,
route: extraContext.route ?? null,
handled: true,
});
}
return result;
🤖 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/observed-action.ts` around lines 77 - 100, The success path in
withAdminObserved is doing unnecessary work on every call by resolving the
observed user and building extra context before confirming there is a soft
failure to record. Update the flow around fn, observeActionSoftFailure, and
resolveObservedUser so you first perform the cheap extractActionSoftFailure
check on result and only then call resolveObservedUser() and
opts.contextFrom?.(args) when there is an actual soft failure to log. Keep the
existing context fields and handled flag, but gate the expensive auth/network
round-trip behind that condition.

Comment on lines +160 to +178
const partnerParts = partnerSummary ? splitPartnerSummary(partnerSummary) : { problem: null, fix: null };

const problem = partnerParts.problem ?? summary ?? null;

const fix =
partnerParts.fix ??
timelineNote ??
(partnerParts.problem && summary ? summary : null);

return {
summary,
partnerSummary,
problem,
fix,
area,
timelineNote,
changeTypes,
};
}

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

Problem/Fix collapse to duplicate text when only "Summary" is filled.

When a PR has a ## Summary but no ## Partner-readable summary and no ## Git Activity Timeline note, partnerParts is {problem: null, fix: null}, so:

  • problem = summary (line 162)
  • fix = null ?? null ?? (null && summary ? ... : null) = null (lines 164-167, since partnerParts.problem is null, the final fallback branch is never taken here)

fix ends up null, and WorkTimeline.tsx's TimelineCard (lines 53-57) then falls back to entry.parsed.summary for the "Fix / outcome" card — the exact same text already shown in "Problem". Given the PR copy explicitly nudges authors to fill the partner-readable summary (page.tsx: "no AI summaries"), this will be the common case until authors adopt the new template, so the timeline will regularly show two identical panels per card.

Consider either not falling back to summary for fix when it equals problem, or emitting a distinct placeholder in that case.

🩹 Proposed fix
-  const problem = partnerParts.problem ?? summary ?? null;
-
-  const fix =
-    partnerParts.fix ??
-    timelineNote ??
-    (partnerParts.problem && summary ? summary : null);
+  const problem = partnerParts.problem ?? summary ?? null;
+
+  const fix =
+    partnerParts.fix ??
+    timelineNote ??
+    (partnerParts.problem && summary && summary !== problem ? summary : null);
🤖 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/pr-body-parser.ts` around lines 160 - 178, In
pr-body-parser.ts, the parsing logic in the summary/partnerSummary handling can
produce duplicate Problem and Fix text when only Summary is present. Update the
return flow around splitPartnerSummary, problem, and fix so fix does not fall
back to summary when it would equal problem; instead, leave it null or emit a
distinct placeholder that TimelineCard in WorkTimeline.tsx can display without
duplicating the Problem panel. Ensure the behavior still prefers
partnerParts.fix and timelineNote, and only uses summary as a fallback when it
produces a meaningfully different Fix value.

Comment on lines +158 to +183

it('logs { success: false } soft failures to Helm Bridge without throwing', async () => {
const action = withBaseballAction(
'uploadBaseballDocument',
{ featureArea: 'baseball-documents', feature: 'baseball_documents' },
async () => ({ success: false as const, error: 'Storage bucket unavailable' }),
);

const result = await action();
expect(result).toEqual({ success: false, error: 'Storage bucket unavailable' });
expect(mocks.logServerException).not.toHaveBeenCalled();
expect(mocks.logServerError).toHaveBeenCalledTimes(1);

const softCall = mocks.logServerError.mock.calls[0] as
| [string, Record<string, unknown> | undefined, 'warning' | 'error' | 'critical']
| undefined;
expect(softCall?.[0]).toBe('Storage bucket unavailable');
expect(softCall?.[2]).toBe('error');
expect(softCall?.[1]).toMatchObject({
action: 'uploadBaseballDocument',
sport: 'baseball',
feature: 'baseball_documents',
source: 'server_action',
handled: true,
});
});

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 | ⚡ Quick win

Add a companion test for the expected/auth-noise soft-failure path through the wrapper.

This test only covers the "unexpected" branch ('Storage bucket unavailable'severity: 'error'). The whole point of isExpectedSoftFailureMessage/skipSentry is to suppress noisy-but-expected messages (e.g. 'Not authenticated') at 'warning' with skipSentry: true — that mapping is only unit-tested in isolation in observe-action-result.test.ts, not through the actual withBaseballAction integration path exercised here.

✅ Suggested additional test
+  it('logs expected soft failures as warnings with skipSentry through the wrapper', async () => {
+    const action = withBaseballAction(
+      'loadBaseballRoster',
+      { featureArea: 'baseball-roster', feature: 'baseball_roster' },
+      async () => ({ success: false as const, error: 'Not authenticated' }),
+    );
+
+    await action();
+    const softCall = mocks.logServerError.mock.calls[0] as
+      | [string, Record<string, unknown> | undefined, 'warning' | 'error' | 'critical']
+      | undefined;
+    expect(softCall?.[2]).toBe('warning');
+    expect(softCall?.[1]).toMatchObject({ skipSentry: true });
+  });
📝 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
it('logs { success: false } soft failures to Helm Bridge without throwing', async () => {
const action = withBaseballAction(
'uploadBaseballDocument',
{ featureArea: 'baseball-documents', feature: 'baseball_documents' },
async () => ({ success: false as const, error: 'Storage bucket unavailable' }),
);
const result = await action();
expect(result).toEqual({ success: false, error: 'Storage bucket unavailable' });
expect(mocks.logServerException).not.toHaveBeenCalled();
expect(mocks.logServerError).toHaveBeenCalledTimes(1);
const softCall = mocks.logServerError.mock.calls[0] as
| [string, Record<string, unknown> | undefined, 'warning' | 'error' | 'critical']
| undefined;
expect(softCall?.[0]).toBe('Storage bucket unavailable');
expect(softCall?.[2]).toBe('error');
expect(softCall?.[1]).toMatchObject({
action: 'uploadBaseballDocument',
sport: 'baseball',
feature: 'baseball_documents',
source: 'server_action',
handled: true,
});
});
it('logs { success: false } soft failures to Helm Bridge without throwing', async () => {
const action = withBaseballAction(
'uploadBaseballDocument',
{ featureArea: 'baseball-documents', feature: 'baseball_documents' },
async () => ({ success: false as const, error: 'Storage bucket unavailable' }),
);
const result = await action();
expect(result).toEqual({ success: false, error: 'Storage bucket unavailable' });
expect(mocks.logServerException).not.toHaveBeenCalled();
expect(mocks.logServerError).toHaveBeenCalledTimes(1);
const softCall = mocks.logServerError.mock.calls[0] as
| [string, Record<string, unknown> | undefined, 'warning' | 'error' | 'critical']
| undefined;
expect(softCall?.[0]).toBe('Storage bucket unavailable');
expect(softCall?.[2]).toBe('error');
expect(softCall?.[1]).toMatchObject({
action: 'uploadBaseballDocument',
sport: 'baseball',
feature: 'baseball_documents',
source: 'server_action',
handled: true,
});
});
it('logs expected soft failures as warnings with skipSentry through the wrapper', async () => {
const action = withBaseballAction(
'loadBaseballRoster',
{ featureArea: 'baseball-roster', feature: 'baseball_roster' },
async () => ({ success: false as const, error: 'Not authenticated' }),
);
await action();
const softCall = mocks.logServerError.mock.calls[0] as
| [string, Record<string, unknown> | undefined, 'warning' | 'error' | 'critical']
| undefined;
expect(softCall?.[2]).toBe('warning');
expect(softCall?.[1]).toMatchObject({ skipSentry: true });
});
🤖 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/baseball/__tests__/with-baseball-action-observability.test.ts` around
lines 158 - 183, Add an integration test in withBaseballAction that exercises
the expected/auth-noise soft-failure path, not just the unexpected error branch.
Use withBaseballAction and the existing Helm Bridge mock assertions to verify
that a soft failure like "Not authenticated" is logged via logServerError with
severity "warning" and skipSentry set to true, while still returning { success:
false, error: ... } and preserving the same action metadata shape checked in the
current test.

Comment on lines +181 to +192
observeActionSoftFailure(result, {
action: name,
featureArea,
feature: featureArea.replaceAll('-', '_'),
source: 'server_action',
sport: 'shared',
userId: user.id,
userEmail: user.email ?? null,
teamId: orgId,
handled: true,
tags: { sport: 'lifting', feature: featureArea, lifting_org: orgId },
});

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 | ⚡ Quick win

No lifting-specific integration test for the new observeActionSoftFailure wiring.

src/lib/baseball/with-baseball-action.ts got a dedicated observability test (with-baseball-action-observability.test.ts); this cohort's file list has no equivalent for with-lifting-action.ts. Given the pattern is now established, replicating it here is cheap and closes the gap for this wrapper's soft-failure path.

Want me to draft a with-lifting-action-observability.test.ts mirroring the baseball one?

🤖 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/lifting/with-lifting-action.ts` around lines 181 - 192, Add a
dedicated observability test for with-lifting-action.ts to cover the new
observeActionSoftFailure wiring, mirroring the existing
with-baseball-action-observability.test.ts pattern. Create a
with-lifting-action-observability.test.ts that exercises the withLiftingAction
wrapper’s soft-failure path and asserts observeActionSoftFailure is called with
the expected action, featureArea/feature, source, sport, userId/userEmail,
teamId, handled flag, and tags. Use the unique symbols withLiftingAction and
observeActionSoftFailure to locate the wrapper and keep the test aligned with
the baseball cohort’s coverage.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how sport is used for incident-feed grouping/filtering vs tags.sport.
rg -nP "sport" src/lib/admin/data/incident-feed.ts src/lib/server-error-logger.ts -C2

Repository: njrini99-code/helmv3

Length of output: 4871


🏁 Script executed:

#!/bin/bash
sed -n '150,240p' src/lib/lifting/with-lifting-action.ts
printf '\n--- server-error-logger ---\n'
sed -n '1,280p' src/lib/server-error-logger.ts
printf '\n--- incident-feed ---\n'
sed -n '1,220p' src/lib/admin/data/incident-feed.ts

Repository: njrini99-code/helmv3

Length of output: 17283


🏁 Script executed:

#!/bin/bash
rg -n "function mergeTriage|mergeTriage\(|type AppTriageEventRow|sport" src/lib/admin/data/triage.ts src/lib/admin/incident-grouping.ts src/lib/admin/data -C3

Repository: njrini99-code/helmv3

Length of output: 42853


Keep the persisted sport key aligned with incident-feed grouping src/lib/lifting/with-lifting-action.ts:181-192

admin_events.sport is what the feed filters and buckets on (src/lib/server-error-logger.ts:106,163-169,218; src/lib/admin/data/incident-feed.ts:87; src/lib/admin/data/triage.ts:192,210). tags.sport only survives as Sentry metadata (src/lib/server-error-logger.ts:241). If these soft failures are meant to show up as lifting, the top-level sport value needs to match the grouping key; otherwise the event lands in shared while still carrying a lifting tag.

🤖 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/lifting/with-lifting-action.ts` around lines 181 - 192, The
soft-failure event in withLiftingAction is persisting the wrong sport grouping
key, so it is being stored as shared while only the Sentry tag says lifting.
Update the observeActionSoftFailure call in withLiftingAction to set the
top-level sport field to lifting so admin_events.sport matches the incident-feed
grouping used by server-error-logger, incident-feed, and triage. Keep the
existing tags.sport metadata if needed, but make sure the persisted sport value
and the lifting-specific grouping are aligned.

Comment on lines +151 to +172
/** Infer sport when legacy emitters omit context.sport (most baseball actions pre-withBaseballAction). */
function inferSport(message: string, context: RoundErrorContext): NonNullable<RoundErrorContext['sport']> | null {
if (context.sport) return context.sport;
const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''}`;
const classified = classifyTraceSurface(probe, context.action ?? null);
if (classified.sport) return classified.sport;
if (/baseball/i.test(message)) return 'baseball';
if (/\bgolf\b|coachhelm/i.test(message)) return 'golf';
return null;
}

function enrichTraceContext(message: string, context: RoundErrorContext): RoundErrorContext {
const sport = inferSport(message, context);
if (!sport) return context;
const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''} ${message}`;
const classified = classifyTraceSurface(probe, context.action ?? null);
return {
...context,
sport,
feature: context.feature ?? classified.feature ?? context.featureArea ?? null,
};
}

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

sport and feature can be derived from conflicting classifications.

inferSport() classifies using probe = route+url+action only (Line 154-156), falling back to message regex if classifyTraceSurface finds nothing. enrichTraceContext() then classifies again with a different probe that also includes message (Line 165-166) purely to pick feature. classifyTraceSurface prioritizes baseball over golf internally, so if route/url signal one sport but message text signals the other, the two calls can disagree: the final row gets sport from the first classification and feature from the second, i.e. a baseball_* feature key attached to a golf sport row (or vice versa). This directly feeds admin_events.sport/admin_events.feature, which the new unified incident feed uses for Overview KPIs/triage grouping — a mismatched pair misroutes incidents.

🐛 Proposed fix — classify once, derive feature from the same result as sport
-/** Infer sport when legacy emitters omit context.sport (most baseball actions pre-withBaseballAction). */
-function inferSport(message: string, context: RoundErrorContext): NonNullable<RoundErrorContext['sport']> | null {
-  if (context.sport) return context.sport;
-  const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''}`;
-  const classified = classifyTraceSurface(probe, context.action ?? null);
-  if (classified.sport) return classified.sport;
-  if (/baseball/i.test(message)) return 'baseball';
-  if (/\bgolf\b|coachhelm/i.test(message)) return 'golf';
-  return null;
-}
-
 function enrichTraceContext(message: string, context: RoundErrorContext): RoundErrorContext {
-  const sport = inferSport(message, context);
+  // Classify once, over the same probe (route/url/action + message), so
+  // `sport` and `feature` are always derived from the same call — never
+  // from two different classifications that can disagree on sport.
+  const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''} ${message}`;
+  const classified = classifyTraceSurface(probe, context.action ?? null);
+  const sport =
+    context.sport ??
+    classified.sport ??
+    (/baseball/i.test(message) ? 'baseball' : /\bgolf\b|coachhelm/i.test(message) ? 'golf' : null);
   if (!sport) return context;
-  const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''} ${message}`;
-  const classified = classifyTraceSurface(probe, context.action ?? null);
   return {
     ...context,
     sport,
-    feature: context.feature ?? classified.feature ?? context.featureArea ?? null,
+    feature: context.feature ?? (classified.sport === sport ? classified.feature : null) ?? context.featureArea ?? null,
   };
 }
📝 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
/** Infer sport when legacy emitters omit context.sport (most baseball actions pre-withBaseballAction). */
function inferSport(message: string, context: RoundErrorContext): NonNullable<RoundErrorContext['sport']> | null {
if (context.sport) return context.sport;
const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''}`;
const classified = classifyTraceSurface(probe, context.action ?? null);
if (classified.sport) return classified.sport;
if (/baseball/i.test(message)) return 'baseball';
if (/\bgolf\b|coachhelm/i.test(message)) return 'golf';
return null;
}
function enrichTraceContext(message: string, context: RoundErrorContext): RoundErrorContext {
const sport = inferSport(message, context);
if (!sport) return context;
const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''} ${message}`;
const classified = classifyTraceSurface(probe, context.action ?? null);
return {
...context,
sport,
feature: context.feature ?? classified.feature ?? context.featureArea ?? null,
};
}
function enrichTraceContext(message: string, context: RoundErrorContext): RoundErrorContext {
// Classify once, over the same probe (route/url/action + message), so
// `sport` and `feature` are always derived from the same call — never
// from two different classifications that can disagree on sport.
const probe = `${context.route ?? ''} ${context.url ?? ''} ${context.action ?? ''} ${message}`;
const classified = classifyTraceSurface(probe, context.action ?? null);
const sport =
context.sport ??
classified.sport ??
(/baseball/i.test(message) ? 'baseball' : /\bgolf\b|coachhelm/i.test(message) ? 'golf' : null);
if (!sport) return context;
return {
...context,
sport,
feature: context.feature ?? (classified.sport === sport ? classified.feature : null) ?? context.featureArea ?? null,
};
}
🤖 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/server-error-logger.ts` around lines 151 - 172, The issue is that
inferSport() and enrichTraceContext() classify the same error using different
probes, which can produce conflicting sport and feature values. Update
server-error-logger’s inferSport() and enrichTraceContext() so classification
happens once from a single shared probe/result, then use that same
classification to populate both sport and feature. Keep the logic centered
around classifyTraceSurface() and enrichTraceContext() to ensure
admin_events.sport and admin_events.feature always stay aligned.

@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: 1

🤖 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/baseball/actions/onboarding.ts`:
- Around line 710-713: The onboarding action in `onboarding.ts` revalidates
`/baseball` and `/baseball/player` but does not explicitly cover the new
redirect target `/baseball/player/today`. Update the `revalidatePath` calls in
the onboarding flow so the redirected page is also invalidated, using the
relevant path in the same action that returns `redirectTo:
'/baseball/player/today'`. Keep the existing `revalidatePath` usage in
`onboarding` and add the missing target so the server cache for the destination
stays fresh.
🪄 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: 07f825dc-d7bd-44dc-9050-bdc9de8862f4

📥 Commits

Reviewing files that changed from the base of the PR and between 328af2f and b529d2e.

📒 Files selected for processing (5)
  • src/app/admin/_components/__tests__/admin-nav.test.ts
  • src/app/admin/_components/admin-nav.ts
  • src/app/admin/page.tsx
  • src/app/baseball/actions/__tests__/complete-player-onboarding.test.ts
  • src/app/baseball/actions/onboarding.ts

Comment on lines 710 to +713
revalidatePath('/baseball');
revalidatePath('/baseball/player');

return { success: true, redirectTo: '/baseball/dashboard' };
return { success: true, redirectTo: '/baseball/player/today' };

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 | 🔵 Trivial | ⚡ Quick win

revalidatePath doesn't cover the new redirect target.

The redirect target changed to /baseball/player/today, but the revalidation calls at Line 710-711 only cover /baseball and /baseball/player (default 'page' type). Per Next.js docs: "This will not invalidate pages beneath the specific page." So /baseball/player/today itself isn't explicitly invalidated. Downstream router.refresh() in the caller currently masks this for the client that submitted onboarding, but the server-side Data Cache for that route stays stale until its own next natural revalidation.

💚 Proposed fix
   revalidatePath('/baseball');
   revalidatePath('/baseball/player');
+  revalidatePath('/baseball/player/today');
 
   return { success: true, redirectTo: '/baseball/player/today' };
📝 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
revalidatePath('/baseball');
revalidatePath('/baseball/player');
return { success: true, redirectTo: '/baseball/dashboard' };
return { success: true, redirectTo: '/baseball/player/today' };
revalidatePath('/baseball');
revalidatePath('/baseball/player');
revalidatePath('/baseball/player/today');
return { success: true, redirectTo: '/baseball/player/today' };
🤖 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/baseball/actions/onboarding.ts` around lines 710 - 713, The
onboarding action in `onboarding.ts` revalidates `/baseball` and
`/baseball/player` but does not explicitly cover the new redirect target
`/baseball/player/today`. Update the `revalidatePath` calls in the onboarding
flow so the redirected page is also invalidated, using the relevant path in the
same action that returns `redirectTo: '/baseball/player/today'`. Keep the
existing `revalidatePath` usage in `onboarding` and add the missing target so
the server cache for the destination stays fresh.

njrini99-code added a commit that referenced this pull request Jul 4, 2026
Unified Fairway shell routing and Helm Bridge observability on one branch. Supersedes #786.
@njrini99-code

Copy link
Copy Markdown
Owner Author

Superseded by #787 (squash-merged to main as b544c0f). Closing as duplicate.

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

Labels

security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants