Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions fix_next.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# chore: replace Tailwind v4 arbitrary values with canonical class names

## Why

Since adopting Tailwind v4 the project's default spacing scale now includes built-in
equivalents for many arbitrary values previously required (e.g. `z-[100]` → `z-100`,
`w-[160px]` → `w-40`, `max-h-[400px]` → `max-h-100`). The VS Code Tailwind CSS
IntelliSense extension (`suggestCanonicalClasses` / owner: `tailwindcss-intellisense`)
fires a warning on every one of these occurrences. These warnings surface in every PR
that touches the file, creating ongoing noise and inconsistency across the codebase.
This task cleans them up app-wide in one dedicated sweep rather than piecemeal in every PR.

## No visual regression risk

Every replacement is a pure alias — the compiled CSS output is bit-for-bit identical.
`w-[160px]` and `w-40` both emit `width: 10rem`; `rounded-[4px]` and `rounded-1`
both emit `border-radius: 0.25rem`. The Figma-matched layout is completely unchanged.

## Conversion rule

```
arbitrary [Npx] → canonical -[N÷4] (only when N is divisible by 4)
z-[N] → z-N (integer z-index values)
[calc(...)], hex colors, viewport units → leave unchanged (no canonical form)
```

## Scan result — 52 actionable replacements across 27 files

Exhaustiveness verified: a follow-up pass checked `ring-[Npx]`, `rounded-[Npx]`,
`translate-y-[Npx]`, and `text-[Npx]`. Only `rounded-[4px]` → `rounded-1` was
newly actionable (3 occurrences). All other newly found patterns are non-divisible-by-4
values with no canonical equivalent.

### Z-index (2)
| File | Current | Canonical |
|------|---------|-----------|
| `pages/class-detail/RosterTab.tsx:424` | `z-[100]` | `z-100` |
| `components/ui/navigation-menu.tsx:148` | `z-[1]` | `z-1` |

### Width (11 files, ~18 occurrences)
| File | Current → Canonical |
|------|---------------------|
| `pages/admin/resident-profile/DetailedAttendanceDialog.tsx` | `w-[100px]`→`w-25`, `w-[120px]`→`w-30` (×2) |
| `components/ui/drawer.tsx` | `w-[100px]`→`w-25` |
| `pages/class-detail/RosterTab.tsx` | `w-[140px]`→`w-35` |
| `pages/ClassesPage.tsx` | `w-[140px]`→`w-35`, `w-[180px]`→`w-45`, `w-[200px]`→`w-50` (×2), `w-[220px]`→`w-55` (×2) |
| `pages/program-detail/ProgramOverviewFacilityAdmin.tsx` | `w-[140px]`→`w-35` |
| `pages/class-detail/SupportTab.tsx` | `w-[160px]`→`w-40` |
| `pages/class-detail/EnrollmentHistoryTab.tsx` | `w-[160px]`→`w-40` |
| `components/charts/OperationalInsightsCharts.tsx` | `w-[160px]`→`w-40`, `w-[200px]`→`w-50` |
| `components/student/ActivityHistoryCard.tsx` | `w-[160px]`→`w-40` |
| `pages/programs/ProgramsPage.tsx` | `w-[220px]`→`w-55` (×2), `w-[240px]`→`w-60` (×2) |
| `components/schedule/SessionDetailSheet.tsx` | `w-[400px]`→`w-100`, `w-[500px]`→`w-125` |
| `pages/auth/Login.tsx` | `w-[420px]`→`w-105`, `w-[520px]`→`w-130` |

### Min-width (8 canonical — skip non-integer results like `min-w-[70px]`=17.5)
| File | Current → Canonical |
|------|---------------------|
| `pages/admin/resident-profile/DetailedAttendanceDialog.tsx` | `min-w-[100px]`→`min-w-25` |
| `pages/class-detail/AuditTab.tsx` | `min-w-[80px]`→`min-w-20` |
| `pages/class-detail/TakeAttendanceModal.tsx` | `min-w-[80px]`→`min-w-20` |
| `pages/ClassesPage.tsx` | `min-w-[80px]`→`min-w-20`, `min-w-[100px]`→`min-w-25` (×2) |
| `pages/program-detail/ClassesTab.tsx` | `min-w-[100px]`→`min-w-25` |
| `pages/programs/ClassEvents.tsx` | `min-w-[160px]`→`min-w-40` |
| `pages/programs/ProgramOverviewFacilityAdmin.tsx` | `min-w-[180px]`→`min-w-45` |
| `pages/Dashboard.tsx` | `min-w-[200px]`→`min-w-50` (×4) |
| `components/student/ActivityHistoryCard.tsx` | `min-w-[200px]`→`min-w-50` |
| `components/knowledge-center/OpenContentItemAccordion.tsx` | `min-w-[300px]`→`min-w-75` |
| `components/ui/command.tsx` | `min-w-[500px]`→`min-w-125` |
| `pages/event-attendance/index.tsx` | `min-w-[300px]`→`min-w-75` |

### Height (3)
| File | Current → Canonical |
|------|---------------------|
| `components/charts/OperationalInsightsCharts.tsx` | `h-[280px]`→`h-70` |
| `pages/auth/Login.tsx` | `h-[420px]`→`h-105`, `h-[520px]`→`h-130` |

### Min-height (2)
| File | Current → Canonical |
|------|---------------------|
| `pages/class-detail/ScheduleTab.tsx` | `min-h-[80px]`→`min-h-20` |
| `pages/event-attendance/index.tsx` | `min-h-[80px]`→`min-h-20` |

### Max-height (4 canonical)
| File | Current → Canonical |
|------|---------------------|
| `pages/class-detail/EditClassModal.tsx` | `max-h-[300px]`→`max-h-75` (leave `max-h-[90vh]` as-is) |
| `pages/program-detail/EditProgramDialog.tsx` | `max-h-[300px]`→`max-h-75` (leave `max-h-[90vh]` as-is) |
| `pages/event-attendance/AttendanceRow.tsx` | `max-h-[400px]`→`max-h-100`, `max-h-[800px]`→`max-h-200` |

### Max-width (2 canonical — skip `max-w-[250px]`=62.5 non-integer)
| File | Current → Canonical |
|------|---------------------|
| `pages/class-detail/ScheduleTab.tsx` | `max-w-[600px]`→`max-w-150` |
| `pages/class-detail/ScheduleTab.tsx` | `max-w-[700px]`→`max-w-175` |

### Rounded (3 canonical — skip `rounded-[1px]` and `rounded-[2px]` non-integer)
| File | Current → Canonical |
|------|---------------------|
| `pages/programs/ProgramsPage.tsx:645,704,869` | `rounded-[4px]`→`rounded-1` |

## Out of scope — intentionally kept as arbitrary values

These have no canonical equivalent and must not be changed:

| Pattern | Reason |
|---------|--------|
| `min-w-[70px]`, `min-w-[130px]`, `min-w-[250px]`, `max-w-[250px]` | Non-integer spacing result |
| `pt-[34px]` | 34÷4=8.5, non-integer |
| `rounded-[1px]`, `rounded-[2px]` | Not divisible by 4 |
| `ring-[1px]`, `ring-[3px]` | Not divisible by 4 |
| `translate-y-[2px]` | Not divisible by 4 |
| `text-[10px]` | 10÷4=2.5, non-integer |
| `opacity-[0.07]` | No standard opacity scale entry |
| `max-h-[90vh]` | Viewport unit — no canonical form |
| `top-[1px]`, `p-[3px]` | Not divisible by 4 |
| All `calc(...)` values | No canonical form |
| All hex color values | No canonical form |

## Verification (after implementation)

Build check — should pass with zero errors:
```bash
cd frontend && npm run build
```

Zero-warning grep:
```bash
grep -rn 'z-\[[0-9]\+\]' frontend/src
grep -rn '\(h\|w\|max-w\|min-w\|max-h\|min-h\|rounded\)-\[[0-9]\+px\]' frontend/src
```
Both should return zero results.

## Notes

- `components/ui/` files (e.g. `command.tsx`, `drawer.tsx`, `navigation-menu.tsx`) are project-owned shadcn components — fix them the same as application code
- `class-detail/index.tsx` `z-[100]` and `Schedule.tsx` `h-[600px]` / `max-w-[1400px]` were already fixed in branch `cpride/class-detail-a11y` (ID-640)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify the note about ID-640 changes.

This note states that class-detail/index.tsx z-[100] and Schedule.tsx sizing were "already fixed" in the cpride/class-detail-a11y branch, but those exact changes appear in the current diff (class-detail/index.tsx line 173: z-100, Schedule.tsx lines 350, 367: h-150, max-w-350). Since this PR is also ID-640, the note creates confusion about whether these changes are in scope.

Consider either removing this note or rewording it to clarify that these specific files are being updated in this PR as part of the sticky header work.

🤖 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 `@fix_next.md` at line 137, The note about ID-640 is confusing because it
claims `class-detail/index.tsx` `z-[100]` and `Schedule.tsx` sizing were
"already fixed" in branch cpride/class-detail-a11y, yet the current diff shows
those exact changes (`class-detail/index.tsx` z-100 and `Schedule.tsx` h-150 and
max-w-350) being applied in this PR; update the note to either remove the
statement or reword it to explicitly state that these changes to
`class-detail/index.tsx` (z-100) and `Schedule.tsx` (h-150, max-w-350) are
included and being applied in this PR (ID-640) as part of the sticky header work
rather than being pre-existing in the other branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is because the a11y branch has not been merged. So same changes are being applied multiple times thus the need for this to be fixed app wide. I am creating a PR to clean it up in one sweep. That PR will follow this PR.

- Asana task metadata: Type=Chore/Tech debt · Impact=Low/Limited · Complexity=Small · No dependencies
2 changes: 1 addition & 1 deletion frontend/src/layouts/AuthenticatedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export default function AuthenticatedLayout() {
isKnowledgeCenter ||
(isProgramDetail && canSwitchFacility(user));
const rootClass = 'h-screen bg-background flex overflow-hidden';
const contentClass = `flex-1 min-h-full overflow-y-auto overflow-x-hidden ${needsGrayBg ? 'bg-surface-hover' : ''}`;
const contentClass = `flex-1 min-h-full ${needsGrayBg ? 'bg-surface-hover' : ''}`;

return (
<div className={rootClass}>
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/Schedule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export default function Schedule() {
return (
<div className="space-y-6">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-[600px]" />
<Skeleton className="h-150" />
</div>
);
}
Expand All @@ -363,10 +363,10 @@ export default function Schedule() {
selectedEvent.class_status === SelectedClassStatus.Active;

return (
<div className="bg-[#E7EAED] dark:bg-[#0a0a0a] min-h-screen overflow-x-hidden">
<div className="max-w-[1400px] mx-auto px-8 py-8 space-y-6">
<div className="bg-[#E7EAED] dark:bg-[#0a0a0a] min-h-screen">
<div className="max-w-350 mx-auto px-8 py-8 space-y-6">
{/* Page Header */}
<div className="flex items-center justify-between">
<div className="flex items-center justify-between sticky top-0 z-30 bg-[#E7EAED] dark:bg-[#0a0a0a] py-4 -mx-8 px-8">
<div>
<h1 className="text-2xl text-brand-dark mb-2">
{class_id ? 'Class Schedule' : 'Schedule'}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/class-detail/RosterTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
`/api/program-classes/${classId}/events?all=true`
);

const enrolledRows = enrollmentResp?.data ?? [];

Check warning on line 104 in frontend/src/pages/class-detail/RosterTab.tsx

View workflow job for this annotation

GitHub Actions / Lint and build JS

The 'enrolledRows' logical expression could make the dependencies of useMemo Hook (at line 118) change on every render. To fix this, wrap the initialization of 'enrolledRows' in its own useMemo() Hook

const attendanceMap = useMemo(() => {
return computeAttendanceByUser(eventsResp?.data ?? []);
Expand Down Expand Up @@ -290,7 +290,7 @@
aria-label={`Select ${enrollment.doc_id}`}
className="shrink-0"
/>
<div className="w-[140px] shrink-0">
<div className="w-35 shrink-0">
<div className="text-brand-dark font-medium">
{enrollment.doc_id}
</div>
Expand Down Expand Up @@ -421,7 +421,7 @@
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="z-[100]"
className="z-100"
>
<DropdownMenuItem
onClick={() =>
Expand Down
20 changes: 6 additions & 14 deletions frontend/src/pages/class-detail/SessionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,16 @@ export function SessionRow({

const getIcon = () => {
if (treatAsFrom)
return (
<CalendarClock className="size-5 text-gray-400 flex-shrink-0" />
);
return <CalendarClock className="size-5 text-gray-400 shrink-0" />;
if (treatAsTo)
return (
<CalendarClock className="size-5 text-blue-700 flex-shrink-0" />
);
return <CalendarClock className="size-5 text-blue-700 shrink-0" />;
if (isCancelled)
return (
<CalendarOff className="size-5 text-gray-500 flex-shrink-0" />
);
return <CalendarOff className="size-5 text-gray-500 shrink-0" />;
if (hasAttendance)
return <CheckCircle className="size-5 text-brand flex-shrink-0" />;
return <CheckCircle className="size-5 text-brand shrink-0" />;
if (isPast)
return (
<AlertCircle className="size-5 text-brand-gold flex-shrink-0" />
);
return <Calendar className="size-5 text-gray-400 flex-shrink-0" />;
return <AlertCircle className="size-5 text-brand-gold shrink-0" />;
return <Calendar className="size-5 text-gray-400 shrink-0" />;
};

const showCheckbox =
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/class-detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default function ClassDetailPage() {

return (
<div className="bg-surface-hover min-h-screen">
<div className="bg-white border-b border-gray-200">
<div className="bg-white border-b border-gray-200 sticky top-0 z-30">
<div className="max-w-7xl mx-auto px-6 py-6">
<Breadcrumbs items={breadcrumbItems} />

Expand Down Expand Up @@ -170,7 +170,7 @@ export default function ClassDetailPage() {
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="z-[100]"
className="z-100"
>
<Tooltip>
<TooltipTrigger asChild>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ html {
}

.filter-button {
@apply w-[220px] bg-white border border-gray-200 rounded-lg px-3 py-2 text-sm flex items-center justify-between hover:bg-gray-50 transition-colors cursor-default focus-visible:outline-none focus-visible:border-gray-400 focus-visible:ring-1 focus-visible:ring-gray-400/50;
@apply w-55 bg-white border border-gray-200 rounded-lg px-3 py-2 text-sm flex items-center justify-between hover:bg-gray-50 transition-colors cursor-default focus-visible:outline-none focus-visible:border-gray-400 focus-visible:ring-1 focus-visible:ring-gray-400/50;
}

.media-card {
Expand Down Expand Up @@ -340,11 +340,11 @@ html {
}

.lead-icon {
@apply size-5 text-gray-400 mt-0.5 flex-shrink-0;
@apply size-5 text-gray-400 mt-0.5 shrink-0;
}

.lead-icon-sm {
@apply size-4 text-gray-600 mt-0.5 flex-shrink-0;
@apply size-4 text-gray-600 mt-0.5 shrink-0;
}

.clickable-row {
Expand Down
Loading