Skip to content

fix: Bridge 12-tab mobile sweep — every /admin tab hand-composed at 390px#800

Merged
njrini99-code merged 2 commits into
mainfrom
fix/bridge-mobile
Jul 10, 2026
Merged

fix: Bridge 12-tab mobile sweep — every /admin tab hand-composed at 390px#800
njrini99-code merged 2 commits into
mainfrom
fix/bridge-mobile

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Problem

Owner screenshots (prod, iPhone): Users & Teams and Work log weren't mobile — the page panned sideways (min-w tables with broken min-w-0 ancestor chains), a giant lone "1 OPEN" KPI card, text clipped mid-word, and rows bleeding through the bottom nav (that last one is the shared bg-surface/95 nav bug already fixed in #799 — the Bridge inherits it on merge).

Fix / Outcome

All 12 Bridge tabs audited and fixed against docs/MOBILE_DOCTRINE.md — one fixer per tab, each adversarially verified (4 packets needed repair rounds; final verdicts all correct/correct-with-nits):

  • Rule 8 — card rows below md replace min-w tables: users roster, teams/[id] roster, jobs cron board + integrity grid, deploys list, ben-leah issue table. TeamHealthTable now owns its own card/table split (golf + baseball heal automatically; baseball's local duplicate removed).
  • Width-forcers closedmin-w-0 down every flex/grid ancestor chain, base-tier grid-cols-1 on px-minmax grids, break-words on free-form text (PR titles, deploy refs, error dumps).
  • Monolith KPIs → StatStrip on work/auth/errors/tracer/ben-leah; StatStrip gains MD_LAST_SPAN_RESET_3 (count=3 + mdColumns≥3 releases the trailing span at md — fixes the 768–1023px empty-cell gap).
  • Composed phone treatments — work-log timeline gutter slimmed, jobs triage-first grouping with disclosure + all-clear collapse, health grid folds green/no-data chips per group, deploys show-more cap, ben-leah intake form: 16px inputs (no iOS zoom), 48px targets, Rule-5 fixed CTA bar scoped to form visibility.
  • Hydration-safe times — PanelAllClear + auth timestamps via LocalTime (server-UTC bake-in class).
  • Inset gains the documented href contract (mirrors Surface); Button documents its single-child contract.

Desktop (md+) byte-identical everywhere except the documented StatStrip gap change on auth.

Verification

tsc ✓ · eslint --max-warnings 0 ✓ · vitest 320 admin + 892 fairway ✓ · production build exit 0 ✓

Git Activity Timeline note

Bridge mobile sweep: 12 tabs card-ified/composed for 390px per Mobile Doctrine; shared StatStrip/TeamHealthTable/PanelStates upgrades.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J2E46URHkhCnEQSYDtZLJX

…90px

Owner report (prod, iPhone): Users & Teams and Work log panned sideways
with content bleeding through the bottom nav. Root causes were class-wide,
so every Bridge tab was audited and fixed against docs/MOBILE_DOCTRINE.md
(one fixer per tab, each adversarially verified):

- Rule 8 card rows below md replace min-w tables: users roster (760px),
  teams/[id] roster, jobs cron board + integrity grid, deploys list,
  ben-leah issue table; TeamHealthTable now owns its own card/table split
  (golf + baseball pages heal automatically, baseball's local duplicate
  removed).
- Page-level width-forcers closed: min-w-0 down every flex/grid ancestor
  chain, base-tier grid-cols-1 on px-minmax grids (work log), break-words
  on free-form text (PR titles, deploy refs, error dumps).
- Monolith KPI cards -> StatStrip rhythm on work/auth/errors/tracer/
  ben-leah; StatStrip gains MD_LAST_SPAN_RESET_3 so count=3 + mdColumns>=3
  releases the trailing-cell span at md (fixes the empty-cell gap in the
  768-1023 range).
- Composed phone treatments: work-log timeline gutter slimmed below md,
  jobs triage-first grouping (attention rows surface, routine rows behind
  a disclosure, all-clear collapses), health feature-grid collapses
  green/no-data chips behind a per-group disclosure, deploys show-more cap,
  ben-leah intake form gets 16px inputs (no iOS zoom), 48px touch targets,
  and a Rule-5 fixed CTA bar scoped to the form's viewport presence.
- Hydration-safe times: PanelAllClear + auth timestamps via LocalTime
  (server-UTC bake-in bug class).
- Inset gains the documented href contract (mirrors Surface) for
  tap-through rows; Button JSDoc documents the single-child contract.

Desktop (md+) is byte-identical everywhere except the documented StatStrip
gap change on auth. Gates: tsc, eslint --max-warnings 0, vitest (320 admin
+ 892 fairway), production build — all green.

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

vercel Bot commented Jul 10, 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 10, 2026 7:13pm

Request Review

@cursor

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

Copy link
Copy Markdown

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


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

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added mobile-friendly card/list layouts across Teams, Roster, Issues, Deployments, Jobs, and Integrity checks.
    • Introduced “Show more” behavior for long deployments.
    • Added a mobile fixed submit bar and touch-friendly form controls.
  • Bug Fixes
    • Corrected administrative timestamp rendering using local formatting.
    • Refined error/traceability coverage so metrics reflect app-only incidents.
  • Responsive Design
    • Improved KPI strip layouts and feature health chip summaries, including mobile disclosure/toggle behavior and better wrapping/truncation.

Walkthrough

This PR updates admin interfaces with responsive mobile cards, collapsible lists, shared KPI strips, localized timestamps, improved truncation, mobile form actions, and corrected app-only error coverage metrics.

Changes

Admin responsive UI

Layer / File(s) Summary
Shared display foundations
src/app/admin/_components/LocalTime.tsx, src/app/admin/_components/PanelStates.tsx, src/app/admin/auth/page.tsx, src/components/fairway/...
Adds localized date formatting, replaces direct timestamp formatting, standardizes KPI strips, and documents Button and Inset contracts.
Health and status surfaces
src/app/admin/_components/FeatureDotGrid.tsx, src/app/admin/_components/TeamHealthTable.tsx, src/app/admin/baseball/page.tsx, src/app/admin/errors/..., src/app/admin/health/page.tsx
Adds mobile feature disclosure and team cards, typed team links, app-only traceability metrics, constrained stack traces, and responsive headings.
Ben-Leah mobile form and issue flow
src/app/admin/ben-leah/...
Adds mobile-safe controls, an intersection-aware fixed submit bar, responsive KPI and issue cards, workflow error wrapping, and description clamping.
Deployment and operations mobile views
src/app/admin/deploys/..., src/app/admin/jobs/page.tsx
Adds expandable deployment cards and mobile cron/integrity cards while retaining desktop tables.
Roster and work layouts
src/app/admin/teams/..., src/app/admin/users/..., src/app/admin/golf/tracer/..., src/app/admin/work/...
Adds linked mobile roster rows, truncation constraints, responsive KPI strips, and narrow-screen work and tracer layouts.

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

Possibly related PRs

Suggested labels: design-system

🚥 Pre-merge checks | ✅ 3 | ❌ 9

❌ Failed checks (2 warnings, 7 inconclusive)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title matches the mobile admin sweep, but it omits one of the required Conventional Commit scopes. Change it to a scoped Conventional Commit, e.g. fix(design-system): Bridge 12-tab mobile sweep — every /admin tab hand-composed at 390px.
Docstring Coverage ⚠️ Warning Docstring coverage is 41.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
No Service-Role In Client Bundles ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Rls Coverage On New Tables ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Auth Check In Server Actions ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Sport-Prefixed Table Names ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No Destructive Writes ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No Edits To Historical Migrations ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Conventional Commits ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description is clearly about the same 12-tab mobile layout sweep and related shared UI fixes.
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.
✨ 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/bridge-mobile
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)

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

🔧 ESLint

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

src/app/admin/_components/FeatureDotGrid.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/app/admin/_components/LocalTime.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/app/admin/_components/PanelStates.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 25 others

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

@qodo-code-review

qodo-code-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 98 rules

Grey Divider


Action required

1. Hydration-unsafe timestamp rendering ✓ Resolved 🐞 Bug ☼ Reliability
Description
BenLeahIssueTable (a Client Component) renders "Updated/Closed" timestamps using
toLocaleString() via formatWhen(), which can produce different strings on the server vs the
viewer’s browser and trigger hydration text mismatches/flicker. This becomes more user-visible with
the new below-md card layout that prominently displays these timestamps.
Code

src/app/admin/ben-leah/BenLeahIssueTable.tsx[R117-120]

+      <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-xs text-warm-500">
+        <span>Updated {formatWhen(issue.updated_at)}</span>
+        {issue.closed_at ? <span>Closed {formatWhen(issue.closed_at)}</span> : null}
+      </div>
Relevance

⭐⭐⭐ High

PR #729 added LocalTime to prevent toLocaleString hydration mismatches; treated as real defect
class.

PR-#729

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The component is a Client Component and renders timestamp strings via toLocaleString() during
render, which LocalTime.tsx documents as a deterministic SSR-vs-hydration mismatch class when
server and browser timezones/locales differ.

src/app/admin/ben-leah/BenLeahIssueTable.tsx[1-34]
src/app/admin/ben-leah/BenLeahIssueTable.tsx[117-120]
src/app/admin/_components/LocalTime.tsx[7-29]

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

## Issue description
`BenLeahIssueTable.tsx` is a Client Component and formats dates during render with `new Date(...).toLocaleString()`. Because SSR runs in the server timezone/locale (commonly UTC) and hydration runs in the viewer’s locale/timezone, the rendered text can differ deterministically and cause hydration mismatches or visible timestamp swaps.

## Issue Context
The repo already contains `LocalTime`, explicitly designed to avoid this class of mismatch by rendering a deterministic placeholder on SSR/first client pass and then localizing post-hydration.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueTable.tsx[1-34]
- src/app/admin/ben-leah/BenLeahIssueTable.tsx[117-120]
- src/app/admin/_components/LocalTime.tsx[7-46]

## Proposed fix
- Replace `formatWhen()` usage in both the mobile card and desktop table with `<LocalTime iso={...} variant="datetime" />` (and optionally wrap with surrounding text like `Updated <LocalTime .../>`).
- Remove (or stop using) `formatWhen()` to avoid `toLocaleString()` in the client render path.

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


2. BenLeahIssueTable header not sticky ✓ Resolved 📜 Skill insight ✧ Quality
Description
The desktop data table in BenLeahIssueTable renders a non-sticky <thead>, so headers scroll out
of view while the body scrolls. This violates the requirement that data tables include sticky
headers.
Code

src/app/admin/ben-leah/BenLeahIssueTable.tsx[R145-156]

+          <thead>
+            <tr className="text-left text-xs uppercase tracking-widest text-warm-500">
+              <th className="sticky left-0 z-10 bg-surface-sunken py-2 pl-3 pr-3">Issue</th>
+              <th className="px-3">Derived status</th>
+              <th className="px-3">Set workflow</th>
+              <th className="px-3">Type</th>
+              <th className="px-3">Priority</th>
+              <th className="px-3">Category</th>
+              <th className="px-3">Updated</th>
+              <th className="px-3">Closed</th>
+            </tr>
+          </thead>
Relevance

⭐⭐ Medium

No historical evidence enforcing sticky <thead>/top-0; repo often only makes first column sticky.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519438 requires a sticky table header (e.g., sticky top-0). The cited table
markup renders <thead> without any sticky positioning classes.

src/app/admin/ben-leah/BenLeahIssueTable.tsx[143-156]
Skill: modern-saas-ui

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 table header is not sticky, violating the data-table sticky header requirement.

## Issue Context
Sticky headers improve scanability for long tables and are required by the compliance checklist.

## Fix Focus Areas
- src/app/admin/ben-leah/BenLeahIssueTable.tsx[143-156]

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



Remediation recommended

3. Server-timezone timestamps unlabeled ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new Jobs below-md card views format "Last run" times with new Date(...).toLocaleString() in
a Server Component, so the displayed time is baked using the server runtime timezone/locale and
shown without an explicit timezone marker. This is inconsistent with other Bridge surfaces now using
LocalTime for viewer-local display and can cause misinterpretation of recency.
Code

src/app/admin/jobs/page.tsx[R78-80]

+        <StatLine label="Last run" value={row.lastRunAt ? new Date(row.lastRunAt).toLocaleString() : 'awaiting first run'} />
+        <StatLine label="Duration" value={formatDuration(row.lastDurationMs)} />
+        <StatLine label="Cadence" value={`${row.cadenceMinutes}m`} />
Relevance

⭐⭐⭐ High

PR #729 explicitly fixed server-timezone timestamp issues using LocalTime; likely expected here too.

PR-#729

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
jobs/page.tsx uses toLocaleString() to render timestamps in the new card components, while
LocalTime.tsx documents that runtime timezone differences (server vs viewer) are a known class of
defects the codebase is actively addressing elsewhere.

src/app/admin/jobs/page.tsx[67-81]
src/app/admin/jobs/page.tsx[195-208]
src/app/admin/_components/LocalTime.tsx[12-21]

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

## Issue description
Jobs mobile cards render timestamps with `toLocaleString()` in a Server Component. That output is generated in the server runtime timezone/locale and shipped as-is, with no timezone marker, which can be misleading and inconsistent with nearby panels that now use `LocalTime`.

## Issue Context
`LocalTime` already exists to avoid timezone bake-in and (when used in client boundaries) hydration mismatch issues.

## Fix Focus Areas
- src/app/admin/jobs/page.tsx[67-81]
- src/app/admin/jobs/page.tsx[195-208]
- src/app/admin/_components/LocalTime.tsx[7-46]

## Proposed fix (pick one)
1) Viewer-local (recommended for consistency):
- Import `LocalTime` into `src/app/admin/jobs/page.tsx` and replace the `toLocaleString()` usages in `CronJobCard` and `IntegrityCheckCard` with `<LocalTime iso={...} variant="datetime" />`.

2) Explicit UTC (if UTC is intended here):
- Format with a fixed `timeZone: 'UTC'` and include a visible `UTC` suffix (e.g. `Jul 10, 07:00 UTC`) so the user can’t mistake the timezone.

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


4. <summary> touch target too small ✓ Resolved 📜 Skill insight ☑ Accessibility
Description
The new collapsible sections use <summary> elements styled as text-xs without padding or
min-height, which is likely below the 44px minimum touch target on mobile. This violates the minimum
touch target requirement and can harm accessibility/usability.
Code

src/app/admin/jobs/page.tsx[R119-121]

+          <summary className="cursor-pointer text-xs text-warm-700 underline decoration-dotted decoration-warm-400 marker:text-warm-400">
+            {restRows.length} other job{restRows.length === 1 ? '' : 's'} — view schedule
+          </summary>
Relevance

⭐⭐ Medium

Repo uses min-h-44/48 for touch targets elsewhere, but no accepted history specifically on <summary>
padding/size.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519432 requires interactive touch targets to be at least 44px. The cited
<summary> elements are interactive but have no padding/min-height classes that would ensure the
required minimum size.

src/app/admin/jobs/page.tsx[118-121]
src/app/admin/jobs/page.tsx[210-213]
Skill: modern-saas-ui

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

## Issue description
Interactive `<summary>` elements are introduced without any `min-h-[44px]` or padding that would guarantee a 44px tap target.

## Issue Context
PR Compliance requires interactive elements to be at least 44px to be reliably tappable on mobile.

## Fix Focus Areas
- src/app/admin/jobs/page.tsx[118-121]
- src/app/admin/jobs/page.tsx[210-213]

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



Informational

5. mt-2.5 violates spacing scale 📜 Skill insight ⚙ Maintainability
Description
New Tailwind spacing utilities like mt-2.5, space-y-1.5, and gap-1.5 are used in the modified
UI, but the spacing rule restricts spacing to the defined scale tokens. This creates inconsistent
layout rhythm and violates the tokenized spacing requirement.
Code

src/app/admin/jobs/page.tsx[R77-81]

+      <div className="mt-2.5 space-y-1.5 text-xs">
+        <StatLine label="Last run" value={row.lastRunAt ? new Date(row.lastRunAt).toLocaleString() : 'awaiting first run'} />
+        <StatLine label="Duration" value={formatDuration(row.lastDurationMs)} />
+        <StatLine label="Cadence" value={`${row.cadenceMinutes}m`} />
+      </div>
Relevance

⭐ Low

Token/utility strictness suggestions commonly rejected; no evidence of enforced Tailwind
spacing-scale policing.

PR-#274
PR-#797

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519417 requires padding/margin/gap to use the defined spacing scale. The cited
code uses fractional Tailwind spacing utilities (mt-2.5, space-y-1.5, gap-1.5) which are
off-scale.

src/app/admin/jobs/page.tsx[77-81]
src/app/admin/deploys/page.tsx[233-248]
src/app/admin/users/page.tsx[121-131]
Skill: modern-saas-ui

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 code uses non-token spacing classes (e.g., `mt-2.5`, `space-y-1.5`, `gap-1.5`) which violate the defined spacing scale.

## Issue Context
PR Compliance requires spacing to stay on the approved spacing scale; fractional Tailwind spacing values introduce off-scale spacing.

## Fix Focus Areas
- src/app/admin/jobs/page.tsx[77-81]
- src/app/admin/deploys/page.tsx[233-248]
- src/app/admin/users/page.tsx[121-131]

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


Grey Divider

Qodo Logo

Comment thread src/app/admin/jobs/page.tsx Outdated
Comment thread src/app/admin/ben-leah/BenLeahIssueTable.tsx Outdated
Comment thread src/app/admin/ben-leah/BenLeahIssueTable.tsx
Comment thread src/app/admin/jobs/page.tsx Outdated
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown

Greptile Summary

A pure mobile-responsiveness sweep across all 12 admin Bridge tabs: no data layer, auth, or RLS changes. Every fix follows the documented Mobile Doctrine \u2014 doctrine-8 card/table splits below md, StatStrip for KPI rows, min-w-0 chain repairs, break-words on free-form text, and LocalTime for hydration-safe timestamps.

  • Card/table splits \u2014 users roster, teams/[id] roster, jobs cron board + integrity grid, deploys list, and ben-leah issue table all gain below-md card views; desktop tables are byte-identical.
  • StatStrip rollout \u2014 work/auth/errors/tracer/ben-leah KPI rows migrated; MD_LAST_SPAN_RESET_3 added to StatStrip to fix the 768\u20131023px empty-cell gap when count=3 and mdColumns\u22653.
  • Timezone fix \u2014 PanelAllClear and auth timestamps corrected via LocalTime; three newly-added server-component phone-card timestamps (CronJobCard, IntegrityCheckCard, TeamHealthTable) still use toLocaleString() / toLocaleDateString() directly.

Confidence Score: 4/5

Safe to merge — pure UI refactor with no data layer, auth, or migration changes; all 12 admin tab card/table splits are correctly fenced behind responsive breakpoints.

The sweep is correctly executed and well-documented. Two inconsistencies are worth cleaning up: ShowMoreList imports the raw shadcn button rather than the fairway wrapper, and three new phone-card timestamp renders in jobs/page.tsx and TeamHealthTable still call toLocaleString() in server components, which the PR otherwise makes a point of fixing via LocalTime.

src/app/admin/deploys/_components/ShowMoreList.tsx (button import) and src/app/admin/jobs/page.tsx / src/app/admin/_components/TeamHealthTable.tsx (server-component timestamp rendering in new phone-card views).

Important Files Changed

Filename Overview
src/app/admin/deploys/_components/ShowMoreList.tsx New client leaf for collapsible deploy list; logic is sound but imports Button from the raw shadcn primitive instead of @/components/fairway.
src/app/admin/jobs/page.tsx Adds triage-first CronBoardCards and IntegrityCards phone views; all-clear timestamps use LocalTime correctly, but per-row Last run timestamps still call toLocaleString() in a server component.
src/app/admin/_components/TeamHealthTable.tsx Gains its own doctrine-8 card/table split; the new phone card uses toLocaleDateString() on lastActivity in a server component rather than LocalTime.
src/components/fairway/charts/StatStrip.tsx Adds MD_LAST_SPAN_RESET_3 to fix the 768-1023px empty-cell gap when count===3 and mdColumns>=3; correctly scoped.
src/app/admin/_components/PanelStates.tsx PanelAllClear switches from toLocaleTimeString() to LocalTime, correctly fixing the server-UTC hydration bug.
src/app/admin/ben-leah/BenLeahForm.tsx Adds IntersectionObserver-gated fixed CTA bar; default ctaInView=true prevents hydration mismatch, pb-28 reserve scoped to mobile only.
src/app/admin/auth/page.tsx Converts KPI cards to StatStrip and replaces direct toLocaleTimeString/toLocaleString with LocalTime on locked-until and created_at timestamps.
src/app/admin/errors/page.tsx ErrorTraceabilityStrip refactored to take pre-filtered appIncidents, preventing a 0/1=0% strip when only Sentry-origin incidents exist.
src/app/admin/users/page.tsx Adds doctrine-8 player card rows below md and fixes min-w-0 on truncating flex items; desktop table unchanged.
src/app/admin/ben-leah/BenLeahIssueTable.tsx Adds IssueCard phone view with break-words on issue titles; desktop table byte-identical to before.
src/components/fairway/surfaces/surface.tsx Adds href to InsetProps to document the Inset-as-Link tap-through pattern; no logic change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Phone["Below md (390px)"]
        A[Card rows] --> B[Identity + key stats\nbreak-words, min-w-0]
        B --> C[Tap-through link\nInset as=Link]
        D[KPI row] --> E[StatStrip\n2-col + 1-wide trailing]
        F[Long list] --> G[ShowMoreList\ncollapse after N rows]
    end
    subgraph Desktop["md and up"]
        I[min-w table\noverflow-x-auto scoped\nsticky first col]
        J[StatStrip mdColumns grid]
        K[Full list]
    end
    Phone -- "hidden md:block swap" --> Desktop
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    subgraph Phone["Below md (390px)"]
        A[Card rows] --> B[Identity + key stats\nbreak-words, min-w-0]
        B --> C[Tap-through link\nInset as=Link]
        D[KPI row] --> E[StatStrip\n2-col + 1-wide trailing]
        F[Long list] --> G[ShowMoreList\ncollapse after N rows]
    end
    subgraph Desktop["md and up"]
        I[min-w table\noverflow-x-auto scoped\nsticky first col]
        J[StatStrip mdColumns grid]
        K[Full list]
    end
    Phone -- "hidden md:block swap" --> Desktop
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/app/admin/deploys/_components/ShowMoreList.tsx:4
**Wrong button import — bypasses the fairway design system**

`ShowMoreList` imports `Button` from `@/components/ui/button` (the raw shadcn/ui primitive) instead of `@/components/fairway`. Every other button reference in this PR — `FeatureDotGrid`, `BenLeahForm`, `CronBoardCards` — uses the fairway `Button`, which carries the Helm design token layer. The shadcn primitive renders with different defaults and will produce a visually inconsistent "Show N more deployments" control on the deploys tab compared to every other ghost-button on the Bridge.

```suggestion
import { Button } from '@/components/fairway';
```

### Issue 2 of 2
src/app/admin/jobs/page.tsx:70-73
**Timezone-unsafe timestamps in new server-component phone cards**

`CronJobCard` uses `new Date(row.lastRunAt).toLocaleString()` and `IntegrityCheckCard` uses `new Date(check.lastRunAt).toLocaleString()` inside server-component renders. These execute at request time on the Vercel runtime (UTC), baking UTC timestamps into the HTML — the same bug class this PR explicitly fixes in `PanelAllClear` and the auth page via `LocalTime`. An overdue-job diagnosis from a phone at 8 PM local time showing "Last run: 4/1/2026, 3:00:00 AM" is confusing when the server clock is UTC.

`TeamHealthTable`'s new phone card (`new Date(t.lastActivity).toLocaleDateString()`) has the same issue, though date-only is less operationally misleading near midnight. The three `toLocaleString()` / `toLocaleDateString()` calls in the newly added phone views should go through `LocalTime` to match the pattern established in this PR.

Reviews (1): Last reviewed commit: "fix: Bridge 12-tab mobile sweep — every ..." | Re-trigger Greptile

Comment on lines +70 to +73
<Inset padding="sm" className={cn(isAlarm && 'ring-1 ring-fw-danger/30')}>
<div className="flex items-start justify-between gap-3">
<p className="min-w-0 break-words font-fw-mono text-xs font-medium text-warm-900">{row.jobType}</p>
<StatusPill tone={CRON_STATUS_TONE[row.status]} dot size="sm" className="shrink-0">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Timezone-unsafe timestamps in new server-component phone cards

CronJobCard uses new Date(row.lastRunAt).toLocaleString() and IntegrityCheckCard uses new Date(check.lastRunAt).toLocaleString() inside server-component renders. These execute at request time on the Vercel runtime (UTC), baking UTC timestamps into the HTML — the same bug class this PR explicitly fixes in PanelAllClear and the auth page via LocalTime. An overdue-job diagnosis from a phone at 8 PM local time showing "Last run: 4/1/2026, 3:00:00 AM" is confusing when the server clock is UTC.

TeamHealthTable's new phone card (new Date(t.lastActivity).toLocaleDateString()) has the same issue, though date-only is less operationally misleading near midnight. The three toLocaleString() / toLocaleDateString() calls in the newly added phone views should go through LocalTime to match the pattern established in this PR.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/admin/jobs/page.tsx
Line: 70-73

Comment:
**Timezone-unsafe timestamps in new server-component phone cards**

`CronJobCard` uses `new Date(row.lastRunAt).toLocaleString()` and `IntegrityCheckCard` uses `new Date(check.lastRunAt).toLocaleString()` inside server-component renders. These execute at request time on the Vercel runtime (UTC), baking UTC timestamps into the HTML — the same bug class this PR explicitly fixes in `PanelAllClear` and the auth page via `LocalTime`. An overdue-job diagnosis from a phone at 8 PM local time showing "Last run: 4/1/2026, 3:00:00 AM" is confusing when the server clock is UTC.

`TeamHealthTable`'s new phone card (`new Date(t.lastActivity).toLocaleDateString()`) has the same issue, though date-only is less operationally misleading near midnight. The three `toLocaleString()` / `toLocaleDateString()` calls in the newly added phone views should go through `LocalTime` to match the pattern established in this PR.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix Bridge /admin mobile layouts (cards, StatStrip, overflow fixes)

🐞 Bug fix ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Replace phone-unfriendly tables with card rows below md across Bridge admin tabs.
• Standardize KPI rows via StatStrip and fix the 3-tile tablet-width span gap.
• Eliminate horizontal pan and hydration-time issues with wrapping, min-w-0, and LocalTime.
Diagram

graph TD
  A["Bridge /admin tabs"] --> B{{"Rule 8: cards < md"}} --> C["Table split comps"]
  A --> D["StatStrip KPIs"] --> E["md span fix"]
  A --> F["LocalTime"]
  A --> G["Phone compositions"]
  subgraph Legend
    direction LR
    _tab(["Tab/Page"]) ~~~ _comp(["Shared component"]) ~~~ _fix{{"Doctrine fix"}}
  end
Loading
High-Level Assessment

The approach is appropriate: extend shared primitives (StatStrip, TeamHealthTable, LocalTime, Inset contracts) and apply per-tab mobile compositions where data shapes differ. A generic “ResponsiveTable” abstraction would likely fight the doctrine’s per-surface composition needs and isn’t clearly superior to the targeted splits introduced here (plus ShowMoreList for minimal client state).

Files changed (27) +1312 / -412

Enhancement (5) +146 / -25
TeamHealthTable.tsxAdd mobile card rows; keep md+ sticky table unchanged +48/-7

Add mobile card rows; keep md+ sticky table unchanged

• Introduces a below-'md' card list (tap-through rows) and preserves the existing sticky-identity table for 'md' and up. Centralizes the responsive split so all callers inherit the mobile fix.

src/app/admin/_components/TeamHealthTable.tsx

BenLeahIssueBoard.tsxReplace KPI grid with StatStrip for mobile composition +23/-3

Replace KPI grid with StatStrip for mobile composition

• Moves the 4 KPI tiles into 'StatStrip' to avoid stacked monolith cards on phones and standardize KPI rhythm.

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

ShowMoreList.tsxAdd ShowMoreList client leaf for collapsed mobile lists +51/-0

Add ShowMoreList client leaf for collapsed mobile lists

• Introduces a small client component that owns only an 'expanded' boolean and renders a single '<ul>' with optional extra items. Enables mobile “cap the scroll” behavior without turning whole pages into client components.

src/app/admin/deploys/_components/ShowMoreList.tsx

page.tsxTracer KPIs migrated to StatStrip; reorder for triage-first mobile +20/-15

Tracer KPIs migrated to StatStrip; reorder for triage-first mobile

• Replaces a hand-rolled 5-wide KPI grid with 'StatStrip', pinning 'mdColumns=5' for desktop parity while producing a single mobile rail. Reorders tiles to show “Stuck rounds” first for faster triage.

src/app/admin/golf/tracer/page.tsx

surface.tsxExtend InsetProps with href to support Inset-as-Link rows +4/-0

Extend InsetProps with href to support Inset-as-Link rows

• Adds an 'href?: string' prop to 'InsetProps', documenting and enabling '<Inset as={Link} href=...>' tap-through card rows consistent with Surface’s contract.

src/components/fairway/surfaces/surface.tsx

Bug fix (20) +1143 / -383
FeatureDotGrid.tsxCollapse healthy/no-data feature chips behind disclosure on mobile +115/-14

Collapse healthy/no-data feature chips behind disclosure on mobile

• Adds a rollup header with red/amber/healthy/no-data counts and collapses green+neutral chips behind a below-'md' disclosure. Fixes FeatureChip row layout by providing a single flex-wrapped child to Button and improves mobile width containment.

src/app/admin/_components/FeatureDotGrid.tsx

PanelStates.tsxMake PanelAllClear timestamp hydration-safe via LocalTime +6/-1

Make PanelAllClear timestamp hydration-safe via LocalTime

• Replaces inline 'toLocaleTimeString()' with 'LocalTime' to prevent server-UTC vs client-local hydration mismatches in all-clear panels.

src/app/admin/_components/PanelStates.tsx

page.tsxAuth tab: StatStrip KPI row + LocalTime for timestamps +13/-4

Auth tab: StatStrip KPI row + LocalTime for timestamps

• Migrates the signup-funnel KPI grid to 'StatStrip' for phone-friendly layout while preserving 'md+' columns. Replaces lockout/session timestamps with 'LocalTime' for hydration-safe rendering.

src/app/admin/auth/page.tsx

BenLeahForm.tsxFix iOS zoom-on-focus; add scoped fixed mobile CTA bar +84/-7

Fix iOS zoom-on-focus; add scoped fixed mobile CTA bar

• Restores 16px input text below 'md' and raises touch targets to avoid Safari zoom. Adds a fixed thumb-zone submit bar gated by an IntersectionObserver so it only appears while the form is in view; reserves scroll space via padding.

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

BenLeahIssueTable.tsxRender issues as cards below md; keep desktop table at md+ +87/-21

Render issues as cards below md; keep desktop table at md+

• Adds an 'IssueCard' layout for mobile (wrapped titles, key metadata, workflow select, GitHub link). Keeps the original dense table behind an 'md:block' wrapper.

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

BenLeahIssueWorkflowSelect.tsxMake workflow select iOS-safe on mobile cards +7/-2

Make workflow select iOS-safe on mobile cards

• Increases min-height/tap target and uses 'text-base' below 'md' to avoid iOS zoom-on-focus. Adds 'break-words' to error text to prevent overflow.

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

page.tsxClamp masthead copy on mobile to keep form above fold +5/-1

Clamp masthead copy on mobile to keep form above fold

• Condenses the intro paragraph below 'md' using 'line-clamp' per doctrine rule 2 so the intake form remains reachable earlier.

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

page.tsxDeploys: mobile cards with show-more cap; preserve md+ table +191/-85

Deploys: mobile cards with show-more cap; preserve md+ table

• Adds a below-'md' card list capped at 5 items with a Show more toggle while keeping the existing sticky table at 'md+'. Fixes width forcing by adding 'break-words' to the running-build branch line and uses 'Inset' for card composition.

src/app/admin/deploys/page.tsx

page.tsxPrevent stack trace blocks from forcing page horizontal pan +6/-1

Prevent stack trace blocks from forcing page horizontal pan

• Adds 'w-full min-w-0' to stack trace '<pre>' so overflow remains contained in its own scroll region rather than widening ancestors.

src/app/admin/errors/[fingerprint]/page.tsx

page.tsxTraceability strip: use app-only incidents + StatStrip layout +36/-7

Traceability strip: use app-only incidents + StatStrip layout

• Fixes a logic bug where the traceability strip could render for Sentry-only incidents by computing and guarding on the app-origin subset. Migrates a 5-stat breakdown to 'StatStrip' (snap-rail below 'md', 5 columns at 'md+') and adjusts KPI grid to a 2+1 rhythm below 'sm'.

src/app/admin/errors/page.tsx

TracerPlayerList.tsxImprove narrow-width row layout for long course names +7/-1

Improve narrow-width row layout for long course names

• Uses 'basis-full' below 'sm' so long course names get their own line instead of being squeezed beside pills/counters on phone sheet widths.

src/app/admin/golf/tracer/TracerPlayerList.tsx

page.tsxCondense Feature Health masthead on mobile +10/-2

Condense Feature Health masthead on mobile

• Shrinks the headline to 'text-h3' and hides the descriptive paragraph below 'md' so the feature grid is above-fold reachable on phones.

src/app/admin/health/page.tsx

page.tsxJobs: card-ify cron board and integrity checks below md +250/-88

Jobs: card-ify cron board and integrity checks below md

• Adds card renderers for cron jobs and integrity checks below 'md', including triage-first grouping (alarm rows upfront, routine/passing behind disclosure) and all-clear rollups. Preserves the original 'md+' overflow-contained tables byte-for-byte.

src/app/admin/jobs/page.tsx

RosterTable.tsxTeam roster: mobile card rows below md; keep md+ table +102/-50

Team roster: mobile card rows below md; keep md+ table

• Replaces the below-'md' roster table with tap-through cards (Inset as Link) containing identity and key stats, while retaining the sticky-first-column table at 'md+'. Adds a right-chevron affordance for linked rows.

src/app/admin/teams/[id]/RosterTable.tsx

page.tsxFix flex truncation for long team names +7/-1

Fix flex truncation for long team names

• Adds 'min-w-0' to the 'h1' flex item so 'truncate' can apply and prevent layout breakage with long team names.

src/app/admin/teams/[id]/page.tsx

page.tsxFix flex truncation for long user emails +6/-1

Fix flex truncation for long user emails

• Adds 'min-w-0' to the 'h1' flex item so long emails properly truncate rather than forcing wrapping/reflow.

src/app/admin/users/[id]/page.tsx

page.tsxUsers & Teams: mobile roster cards, StatStrip KPIs, width-forcer fixes +147/-59

Users & Teams: mobile roster cards, StatStrip KPIs, width-forcer fixes

• Replaces the per-team player table with mobile tap-through cards below 'md' and keeps the desktop table behind 'md:block'. Migrates KPI sections to 'StatStrip' (including 5-wide rails) and fixes multiple 'truncate' failures by adding 'min-w-0' on flex-item links/names.

src/app/admin/users/page.tsx

WorkTimeline.tsxWork timeline: slimmer mobile gutter, StatStrip KPIs, wrap long text +46/-32

Work timeline: slimmer mobile gutter, StatStrip KPIs, wrap long text

• Reduces timeline gutter/chrome below 'md', migrates count tiles to 'StatStrip', and moves Top areas into its own full-width card. Adds 'min-w-0', 'break-words', and 'overflow-wrap:anywhere' to prevent long PR titles or paragraphs from forcing horizontal panning.

src/app/admin/work/WorkTimeline.tsx

page.tsxWork log page: remove mobile masthead copy and close min-w-0 chains +8/-6

Work log page: remove mobile masthead copy and close min-w-0 chains

• Hides masthead paragraph below 'md' and adds 'grid-cols-1'/'min-w-0' on layout containers to prevent sideways panning at phone widths.

src/app/admin/work/page.tsx

StatStrip.tsxFix StatStrip 3-tile layout gap on md when mdColumns>=3 +10/-0

Fix StatStrip 3-tile layout gap on md when mdColumns>=3

• Adds 'MD_LAST_SPAN_RESET_3' so the last tile stops spanning 2 columns at 'md' when the grid is already 3+ columns, preventing an empty-cell gap in the 768–1023px range.

src/components/fairway/charts/StatStrip.tsx

Refactor (1) +16 / -4
page.tsxCondense masthead on mobile; type TeamHealthEntry href mapping +16/-4

Condense masthead on mobile; type TeamHealthEntry href mapping

• Adjusts title/paragraph visibility for Mobile Doctrine rule 2 (less masthead chrome below 'md'). Refactors the team href mapping into a typed 'TeamHealthEntry[]' passed to TeamHealthTable.

src/app/admin/baseball/page.tsx

Documentation (1) +7 / -0
button.tsxDocument Button’s single-child (non-asChild) layout contract +7/-0

Document Button’s single-child (non-asChild) layout contract

• Adds documentation warning that Button wraps children in a single span and that multiple siblings can break expected flex-row layouts unless pre-wrapped.

src/components/fairway/controls/button.tsx

…safe timestamps, 44px disclosures, sticky issue-table header

- LocalTime gains a 'date' variant; jobs cron/integrity cards + desktop
  cells and TeamHealthTable's card/cell render viewer-local via LocalTime
  instead of baking the server's UTC clock into HTML (the same class this
  PR fixes in PanelAllClear).
- BenLeahIssueTable (client) formats timestamps post-hydration via a
  private FormattedWhen leaf (custom format preserved) — kills the
  guaranteed server/client hydration mismatch.
- The new phone-card <summary> disclosures get >=44px touch targets.
- BenLeahIssueTable desktop header is sticky inside a bounded scroll
  container (max-h + overflow-auto, matching ImportWizardClient's working
  idiom — sticky top-0 is a no-op without one).

Gates: tsc, eslint, 320 admin tests green in the worktree.

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

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

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

Inline comments:
In `@src/app/admin/_components/FeatureDotGrid.tsx`:
- Around line 82-85: Replace all noncanonical accent-* color classes in
FeatureDotGrid with the appropriate canonical design-token families, using
primary-* for positive/accent states and destructive, warm-*, or cream-* where
semantically applicable. Update the affected class strings and conditional
styling around the identified button/grid sections, ensuring no accent-* tokens
remain.
- Around line 199-210: Update the CTA styling and label logic in the
FeatureDotGrid render block so an all-neutral group is not treated as healthy:
apply the success class and “All …” healthy wording only when counts.green > 0
&& counts.neutral === 0, while retaining the existing needsEyes handling for
other groups.

In `@src/app/admin/_components/TeamHealthTable.tsx`:
- Line 66: Update the non-null timestamp branches in TeamHealthTable’s
last-activity rendering to pass a non-semantic hydration fallback such as “—” or
“loading…” to LocalTime, while retaining “never” only for null lastActivity
branches.
- Around line 55-60: Replace the noncanonical accent color classes in the
TeamHealthTable leader treatment: update isLeader’s `bg-accent-50` and the
dateline span’s `bg-accent-500` to the appropriate approved `primary-*` or
`cream-*` design-system tokens, preserving the existing styling and behavior.

In `@src/app/admin/deploys/_components/ShowMoreList.tsx`:
- Around line 39-47: Add an aria-expanded attribute to the disclosure Button in
ShowMoreList, binding it to the expanded state so assistive technologies receive
the current expanded or collapsed status.

In `@src/app/admin/deploys/page.tsx`:
- Around line 153-156: Extract the shared sentryHref, commitSha, and deployHref
derivation into a reusable helper near the desktop row map and DeploymentCard,
then use that helper in both render paths. Remove the duplicated inline
calculations while preserving the existing link conditions and values.

In `@src/app/admin/errors/`[fingerprint]/page.tsx:
- Around line 69-74: Add keyboard accessibility to the stack-trace <pre> in the
error page: set tabIndex={0}, add an accessible label such as aria-label="Stack
trace", and include a visible focus style in its className alongside
overflow-auto so keyboard users can focus and scroll long traces.

In `@src/app/admin/teams/`[id]/RosterTable.tsx:
- Around line 87-90: Update the jersey-number rendering in the roster row markup
near the `r.name` display to use an explicit null check instead of truthiness,
so numeric value `0` renders as `#0`; keep `'no #'` only for null values and
align the behavior with the desktop rendering path.

In `@src/app/admin/users/page.tsx`:
- Around line 114-115: Preserve jersey number 0 in both roster layouts by
replacing truthiness checks in the mobile and desktop render blocks around
player.jerseyNumber with an explicit null/undefined check, so only missing
values render “no #” while 0 renders as “#0”.

In `@src/components/fairway/surfaces/surface.tsx`:
- Around line 220-223: Prevent the documented href prop from reaching the
default div in the Inset component: destructure href from the props and exclude
it from the generic spread, then conditionally pass it only when the resolved as
component is a non-div link-like element. Update the rendering logic around
Inset’s props handling and element creation near the existing spread.
🪄 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: ce7ce633-8fa3-477a-ae4c-cb9eee6b0553

📥 Commits

Reviewing files that changed from the base of the PR and between 1035e6b and 3d174f5.

📒 Files selected for processing (28)
  • src/app/admin/_components/FeatureDotGrid.tsx
  • src/app/admin/_components/LocalTime.tsx
  • src/app/admin/_components/PanelStates.tsx
  • src/app/admin/_components/TeamHealthTable.tsx
  • src/app/admin/auth/page.tsx
  • src/app/admin/baseball/page.tsx
  • src/app/admin/ben-leah/BenLeahForm.tsx
  • 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
  • src/app/admin/deploys/_components/ShowMoreList.tsx
  • src/app/admin/deploys/page.tsx
  • src/app/admin/errors/[fingerprint]/page.tsx
  • src/app/admin/errors/page.tsx
  • src/app/admin/golf/tracer/TracerPlayerList.tsx
  • src/app/admin/golf/tracer/page.tsx
  • src/app/admin/health/page.tsx
  • src/app/admin/jobs/page.tsx
  • src/app/admin/teams/[id]/RosterTable.tsx
  • src/app/admin/teams/[id]/page.tsx
  • src/app/admin/users/[id]/page.tsx
  • src/app/admin/users/page.tsx
  • src/app/admin/work/WorkTimeline.tsx
  • src/app/admin/work/page.tsx
  • src/components/fairway/charts/StatStrip.tsx
  • src/components/fairway/controls/button.tsx
  • src/components/fairway/surfaces/surface.tsx

Comment on lines +82 to 85
'h-auto min-h-0 w-full rounded-xl border px-3 py-2 text-left normal-case',
richGreen
? 'border-accent-500/35 bg-accent-50/60 hover:bg-accent-50'
: 'border-border-subtle bg-surface',

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

Use the canonical design-token families.

src/app/admin/_components/FeatureDotGrid.tsx:82-85,159-179,202-203 introduces accent-* classes, while the repository rules require primary-*, destructive, warm-*, and cream-* tokens. Replace these with the corresponding canonical tokens.

[details]

- 'border-accent-500/35 bg-accent-50/60 hover:bg-accent-50'
+ 'border-primary-600/35 bg-cream-50/60 hover:bg-cream-50'

[/details]

As per coding guidelines, only canonical design-system color families are permitted.
As per path instructions, src/**/*.{tsx,css} must use canonical design-system tokens.

Also applies to: 159-179, 202-203

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

In `@src/app/admin/_components/FeatureDotGrid.tsx` around lines 82 - 85, Replace
all noncanonical accent-* color classes in FeatureDotGrid with the appropriate
canonical design-token families, using primary-* for positive/accent states and
destructive, warm-*, or cream-* where semantically applicable. Update the
affected class strings and conditional styling around the identified button/grid
sections, ensuring no accent-* tokens remain.

Sources: Coding guidelines, Path instructions

Comment on lines +199 to +210
className={cn(
'mt-2 justify-start whitespace-normal rounded-xl px-3 py-2 text-left text-xs font-medium md:hidden',
needsEyes === 0 && !showHealthy
? 'bg-fw-success-bg text-accent-700 hover:bg-fw-success-bg/80'
: 'text-warm-500 hover:text-warm-700',
)}
>
{showHealthy
? 'Hide healthy & no-data features'
: needsEyes === 0
? `All ${formatCollapsedSummary(counts)} — show list →`
: `Show ${formatCollapsedSummary(counts)} →`}

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

Do not style an all-neutral group as healthy.

At src/app/admin/_components/FeatureDotGrid.tsx:199-210, a group with 0 green and only neutral features satisfies needsEyes === 0, producing a success-colored CTA and the text All 0 healthy · N no data. Use the success treatment only when counts.green > 0 && counts.neutral === 0.

[details]

- needsEyes === 0 && !showHealthy
+ needsEyes === 0 && counts.green > 0 && counts.neutral === 0 && !showHealthy
...
- needsEyes === 0
+ needsEyes === 0 && counts.green > 0 && counts.neutral === 0

[/details]

📝 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
className={cn(
'mt-2 justify-start whitespace-normal rounded-xl px-3 py-2 text-left text-xs font-medium md:hidden',
needsEyes === 0 && !showHealthy
? 'bg-fw-success-bg text-accent-700 hover:bg-fw-success-bg/80'
: 'text-warm-500 hover:text-warm-700',
)}
>
{showHealthy
? 'Hide healthy & no-data features'
: needsEyes === 0
? `All ${formatCollapsedSummary(counts)} — show list →`
: `Show ${formatCollapsedSummary(counts)} →`}
className={cn(
'mt-2 justify-start whitespace-normal rounded-xl px-3 py-2 text-left text-xs font-medium md:hidden',
needsEyes === 0 && counts.green > 0 && counts.neutral === 0 && !showHealthy
? 'bg-fw-success-bg text-accent-700 hover:bg-fw-success-bg/80'
: 'text-warm-500 hover:text-warm-700',
)}
>
{showHealthy
? 'Hide healthy & no-data features'
: needsEyes === 0 && counts.green > 0 && counts.neutral === 0
? `All ${formatCollapsedSummary(counts)} — show list →`
: `Show ${formatCollapsedSummary(counts)} →`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/_components/FeatureDotGrid.tsx` around lines 199 - 210, Update
the CTA styling and label logic in the FeatureDotGrid render block so an
all-neutral group is not treated as healthy: apply the success class and “All …”
healthy wording only when counts.green > 0 && counts.neutral === 0, while
retaining the existing needsEyes handling for other groups.

Comment on lines +55 to +60
'block rounded-fw-md px-2 py-3 transition-colors hover:bg-surface-sunken',
isLeader && 'bg-accent-50',
)}
>
{/* Dateline rule — replaces the retired border-l-2 leader stripe. */}
{isLeader && <span aria-hidden className="mb-1 block h-[2px] w-7 rounded-full bg-accent-500" />}

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

Use canonical tokens for the mobile leader treatment.

src/app/admin/_components/TeamHealthTable.tsx:55-60 adds bg-accent-50 and bg-accent-500. Use the approved primary-* or cream-* equivalents instead.

As per coding guidelines, only canonical design-system color families are permitted.
As per path instructions, src/**/*.{tsx,css} must use canonical design-system tokens.

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

In `@src/app/admin/_components/TeamHealthTable.tsx` around lines 55 - 60, Replace
the noncanonical accent color classes in the TeamHealthTable leader treatment:
update isLeader’s `bg-accent-50` and the dateline span’s `bg-accent-500` to the
appropriate approved `primary-*` or `cream-*` design-system tokens, preserving
the existing styling and behavior.

Sources: Coding guidelines, Path instructions

<p className="truncate text-sm font-medium text-warm-900">{t.name}</p>
<p className="mt-1 font-fw-mono text-xs tabular-nums text-warm-500">
{t.playerCount} players · last{' '}
{t.lastActivity ? <LocalTime iso={t.lastActivity} variant="date" fallback="never" /> : 'never'}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a non-semantic hydration fallback for real timestamps.

LocalTime at src/app/admin/_components/LocalTime.tsx:30-53 renders fallback during SSR and the first client render. With fallback="never", every non-null timestamp initially appears as “last never”; reserve “never” for the existing null branches and use or loading… here.

[details]

- <LocalTime iso={t.lastActivity} variant="date" fallback="never" />
+ <LocalTime iso={t.lastActivity} variant="date" fallback="—" />

[/details]

Also applies to: 112-112

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

In `@src/app/admin/_components/TeamHealthTable.tsx` at line 66, Update the
non-null timestamp branches in TeamHealthTable’s last-activity rendering to pass
a non-semantic hydration fallback such as “—” or “loading…” to LocalTime, while
retaining “never” only for null lastActivity branches.

Comment on lines +39 to +47
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setExpanded((v) => !v)}
className="mt-2 w-full bg-surface-sunken text-warm-700 hover:text-accent-700"
>
{expanded ? 'Show less' : `Show ${moreCount} more ${itemLabel}${moreCount === 1 ? '' : 's'}`}
</Button>

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 aria-expanded to the disclosure toggle.

The button toggles visibility of more but doesn't expose its state to assistive tech. Screen-reader users have no way to know whether the list is currently expanded or collapsed.

♿ Proposed fix
         <Button
           type="button"
           variant="ghost"
           size="sm"
+          aria-expanded={expanded}
           onClick={() => setExpanded((v) => !v)}
           className="mt-2 w-full bg-surface-sunken text-warm-700 hover:text-accent-700"
         >
📝 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
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setExpanded((v) => !v)}
className="mt-2 w-full bg-surface-sunken text-warm-700 hover:text-accent-700"
>
{expanded ? 'Show less' : `Show ${moreCount} more ${itemLabel}${moreCount === 1 ? '' : 's'}`}
</Button>
<Button
type="button"
variant="ghost"
size="sm"
aria-expanded={expanded}
onClick={() => setExpanded((v) => !v)}
className="mt-2 w-full bg-surface-sunken text-warm-700 hover:text-accent-700"
>
{expanded ? 'Show less' : `Show ${moreCount} more ${itemLabel}${moreCount === 1 ? '' : 's'}`}
</Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/admin/deploys/_components/ShowMoreList.tsx` around lines 39 - 47, Add
an aria-expanded attribute to the disclosure Button in ShowMoreList, binding it
to the expanded state so assistive technologies receive the current expanded or
collapsed status.

Comment on lines +153 to +156
{rows.map((d) => {
const sentryHref = sentryReleaseHref(d.commitSha);
const commitSha = d.commitSha;
const deployHref = d.url ? `https://${d.url}` : 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 | 🔵 Trivial | ⚡ Quick win

Duplicated link-derivation logic between the desktop row map and DeploymentCard.

sentryHref/commitSha/deployHref are computed identically in the desktop <tbody> map (Lines 153-156) and again in DeploymentCard (Lines 226-229). Any future change to this derivation (e.g., new link condition) has to be kept in sync by hand in two places.

♻️ Proposed fix
+function deploymentLinks(d: VercelDeployment) {
+  return {
+    sentryHref: sentryReleaseHref(d.commitSha),
+    commitSha: d.commitSha,
+    deployHref: d.url ? `https://${d.url}` : null,
+  };
+}
+
 async function DeploymentsTable() {
   ...
-            {rows.map((d) => {
-              const sentryHref = sentryReleaseHref(d.commitSha);
-              const commitSha = d.commitSha;
-              const deployHref = d.url ? `https://${d.url}` : null;
+            {rows.map((d) => {
+              const { sentryHref, commitSha, deployHref } = deploymentLinks(d);
               return (
 function DeploymentCard({ d }: { d: VercelDeployment }) {
-  const sentryHref = sentryReleaseHref(d.commitSha);
-  const commitSha = d.commitSha;
-  const deployHref = d.url ? `https://${d.url}` : null;
+  const { sentryHref, commitSha, deployHref } = deploymentLinks(d);

Also applies to: 226-229

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

In `@src/app/admin/deploys/page.tsx` around lines 153 - 156, Extract the shared
sentryHref, commitSha, and deployHref derivation into a reusable helper near the
desktop row map and DeploymentCard, then use that helper in both render paths.
Remove the duplicated inline calculations while preserving the existing link
conditions and values.

Comment on lines +69 to +74
// Contained CODE block, never a page-level pan: w-full + min-w-0
// keep it from ever donating its long-line width to an ancestor,
// overflow-auto gives it its own horizontal+vertical scroller
// instead (classic min-w offender otherwise — Mobile Doctrine
// rule 8 territory even though this isn't literally a table).
<pre className="mt-2 max-h-48 w-full min-w-0 overflow-auto rounded bg-warm-100 p-2 text-caption">{e.stack_trace}</pre>

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

Make the contained stack trace keyboard-scrollable.

At src/app/admin/errors/[fingerprint]/page.tsx:74, overflow-auto creates the only scroll container, but <pre> is not focusable. Add tabIndex={0}, an accessible label, and a visible focus style so keyboard users can inspect long traces.

[details]

- <pre className="mt-2 max-h-48 w-full min-w-0 overflow-auto rounded bg-warm-100 p-2 text-caption">
+ <pre
+   tabIndex={0}
+   aria-label="Stack trace"
+   className="mt-2 max-h-48 w-full min-w-0 overflow-auto rounded bg-warm-100 p-2 text-caption focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-600"
+ >

[/details]

📝 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
// Contained CODE block, never a page-level pan: w-full + min-w-0
// keep it from ever donating its long-line width to an ancestor,
// overflow-auto gives it its own horizontal+vertical scroller
// instead (classic min-w offender otherwise — Mobile Doctrine
// rule 8 territory even though this isn't literally a table).
<pre className="mt-2 max-h-48 w-full min-w-0 overflow-auto rounded bg-warm-100 p-2 text-caption">{e.stack_trace}</pre>
<pre
tabIndex={0}
aria-label="Stack trace"
className="mt-2 max-h-48 w-full min-w-0 overflow-auto rounded bg-warm-100 p-2 text-caption focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-600"
>
🤖 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/`[fingerprint]/page.tsx around lines 69 - 74, Add
keyboard accessibility to the stack-trace <pre> in the error page: set
tabIndex={0}, add an accessible label such as aria-label="Stack trace", and
include a visible focus style in its className alongside overflow-auto so
keyboard users can focus and scroll long traces.

Comment on lines +87 to +90
<p className={cn('truncate font-medium', isDormant ? 'text-warm-500' : 'text-warm-900')}>{r.name}</p>
<p className="mt-1 font-fw-mono text-xs tabular-nums text-warm-500">
{r.jerseyNumber ? `#${r.jerseyNumber}` : 'no #'} ·{' '}
{r.lastRoundScore !== 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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve jersey number 0.

src/app/admin/teams/[id]/RosterTable.tsx:89 treats 0 as missing, while the desktop path at Line 144 correctly preserves it with nullish handling. jerseyNumber is typed as number | null, so use an explicit null check for consistent mobile and desktop output.

Proposed fix
-                    {r.jerseyNumber ? `#${r.jerseyNumber}` : 'no #'} ·{' '}
+                    {r.jerseyNumber !== null ? `#${r.jerseyNumber}` : 'no #'} ·{' '}
📝 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
<p className={cn('truncate font-medium', isDormant ? 'text-warm-500' : 'text-warm-900')}>{r.name}</p>
<p className="mt-1 font-fw-mono text-xs tabular-nums text-warm-500">
{r.jerseyNumber ? `#${r.jerseyNumber}` : 'no #'} ·{' '}
{r.lastRoundScore !== null ? (
<p className={cn('truncate font-medium', isDormant ? 'text-warm-500' : 'text-warm-900')}>{r.name}</p>
<p className="mt-1 font-fw-mono text-xs tabular-nums text-warm-500">
{r.jerseyNumber !== null ? `#${r.jerseyNumber}` : 'no #'} ·{' '}
{r.lastRoundScore !== 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/teams/`[id]/RosterTable.tsx around lines 87 - 90, Update the
jersey-number rendering in the roster row markup near the `r.name` display to
use an explicit null check instead of truthiness, so numeric value `0` renders
as `#0`; keep `'no #'` only for null values and align the behavior with the
desktop rendering path.

Comment on lines +114 to +115
player.jerseyNumber ? `#${player.jerseyNumber}` : 'no #',
player.position,

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

Preserve jersey number 0 in both roster layouts.

src/app/admin/users/page.tsx:114 and src/app/admin/users/page.tsx:203 use truthiness checks, so a valid jerseyNumber of 0 renders as no #. Since the field is numeric and both mobile and desktop share the same bug, use an explicit null check.

Proposed fix
-                  player.jerseyNumber ? `#${player.jerseyNumber}` : 'no #',
+                  player.jerseyNumber !== null ? `#${player.jerseyNumber}` : 'no #',

-                        {player.jerseyNumber ? `#${player.jerseyNumber}` : 'no #'}
+                        {player.jerseyNumber !== null ? `#${player.jerseyNumber}` : 'no #'}

Also applies to: 202-204

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

In `@src/app/admin/users/page.tsx` around lines 114 - 115, Preserve jersey number
0 in both roster layouts by replacing truthiness checks in the mobile and
desktop render blocks around player.jerseyNumber with an explicit null/undefined
check, so only missing values render “no #” while 0 renders as “#0”.

Comment on lines +220 to +223
/** Destination when rendered as an anchor / Next `Link` via `as`. Ignored by
* the default `div`. Enables `<Inset as={Link} href=…>` tap-through rows —
* the same contract Surface documents above. */
href?: string;

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

Do not forward href to the default <div>.

The prop is documented as ignored for the default element, but Line 244 still spreads it through. <Inset href="…"> therefore renders an invalid href attribute on a <div>. Destructure href and only pass it when as resolves to a non-div link-like component.

Proposed fix
 export const Inset = forwardRef<HTMLDivElement, InsetProps>(function Inset(
-  { padding = 'sm', as, className, children, ...props },
+  { padding = 'sm', as, href, className, children, ...props },
   ref,
 ) {
   const Comp = (as ?? 'div') as ElementType;
   return (
     <Comp
       ref={ref}
       data-slot="inset"
       className={cn(
         'bg-surface-sunken rounded-fw-md text-text-secondary',
         PADDING[padding],
         className,
       )}
       {...props}
+      {...(Comp === 'div' ? {} : { href })}
     >
📝 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
/** Destination when rendered as an anchor / Next `Link` via `as`. Ignored by
* the default `div`. Enables `<Inset as={Link} href=…>` tap-through rows
* the same contract Surface documents above. */
href?: string;
export const Inset = forwardRef<HTMLDivElement, InsetProps>(function Inset(
{ padding = 'sm', as, href, className, children, ...props },
ref,
) {
const Comp = (as ?? 'div') as ElementType;
return (
<Comp
ref={ref}
data-slot="inset"
className={cn(
'bg-surface-sunken rounded-fw-md text-text-secondary',
PADDING[padding],
className,
)}
{...props}
{...(Comp === 'div' ? {} : { href })}
>
🤖 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/components/fairway/surfaces/surface.tsx` around lines 220 - 223, Prevent
the documented href prop from reaching the default div in the Inset component:
destructure href from the props and exclude it from the generic spread, then
conditionally pass it only when the resolved as component is a non-div link-like
element. Update the rendering logic around Inset’s props handling and element
creation near the existing spread.

@njrini99-code njrini99-code merged commit 9d4ab61 into main Jul 10, 2026
44 checks passed
@njrini99-code njrini99-code deleted the fix/bridge-mobile branch July 10, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant