Skip to content

chore(web-ui): frontend major-dependency upgrades — Jest 30, jsdom 30, ESLint 10, Tailwind 4 (#658)#695

Merged
frankbria merged 5 commits into
mainfrom
chore/658-frontend-major-deps
Jun 19, 2026
Merged

chore(web-ui): frontend major-dependency upgrades — Jest 30, jsdom 30, ESLint 10, Tailwind 4 (#658)#695
frankbria merged 5 commits into
mainfrom
chore/658-frontend-major-deps

Conversation

@frankbria

Copy link
Copy Markdown
Owner

Summary

Closes #658 — the four held-out major-version frontend dependency upgrades. Blocker #646 (CI now runs npm run build + frontend lint) is merged, so all four are verifiable in CI.

Done as one branch, three commits, each independently green:

Commit Upgrade Supersedes
Jest 30 + jsdom 30 jest, @types/jest, jest-environment-jsdom → 30 #641, #642
ESLint 10 eslint → 10 #639
Tailwind 4 tailwindcss → 4 (CSS-first PostCSS pipeline) #640

Notable migration work

Jest 30 / jsdom 30 — jsdom 30 makes window.location non-configurable, so the old Object.defineProperty(window, 'location', …) test stub throws. Added a tiny src/lib/navigation.ts seam (currentPathname / redirectTo); the api.ts 401 redirect and auth.ts logout route through it, and api.auth.test.ts mocks the seam instead of poking window.location. Also re-added json-summary to coverageReporters (Jest 30 dropped it from defaults) so CI's coverage-summary gate keeps working.

ESLint 10 — bumped typescript-eslint (8.61) and eslint-plugin-react-hooks (7.1) to their eslint-10-supporting versions. eslint-plugin-react has no eslint-10 release and contributed only a no-op react/react-in-jsx-scope: off rule (never enabled by the next/recommended configs), so it and the unused eslint-config-next dep were removed. Lint output is unchanged (0 errors, same 3 pre-existing exhaustive-deps warnings).

Tailwind 4@tailwind directives → @import 'tailwindcss' + @config loading the existing JS theme (no CSS-first rewrite); @tailwindcss/postcss replaces the integrated plugin; autoprefixer dropped (v4 vendors prefixes). darkMode: 'class' (v4 dropped the single-element-array form). Applied v4 size-scale renames to preserve appearance/a11y: outline-noneoutline-hidden, shadow-smshadow-xs, rounded-smrounded-xs, blur-smblur-xs.

Verification

  • npm run build ✅ — emits all custom theme utilities, the .dark variant, the typography .prose plugin, and the renamed utilities.
  • npm run test:coverage ✅ — 1020 tests pass, coverage 68.91% (gate 65%).
  • npm run lint ✅ — 0 errors (3 pre-existing warnings, unchanged from baseline).

Known limitations

  • No CSS visual diff in CI. Jest runs under jsdom (no CSS rendering) and next build validates compilation, not pixels. The Tailwind v4 utility renames above were applied to preserve the v3 appearance, but a manual visual pass is still the only thing that would catch subtle theme shifts.
  • Tailwind config stays as the legacy JS file via @config (deliberate — minimal diff). A future CSS-first @theme migration is optional.

Follow-up

Once merged, the superseded Dependabot PRs #639, #640, #641, #642 should be closed.

- Bump jest, @types/jest, jest-environment-jsdom to ^30.
- jsdom 30 makes window.location non-configurable; add a small
  src/lib/navigation seam (currentPathname/redirectTo) and route the
  api.ts 401 redirect and auth.ts logout through it so redirects stay
  mockable. api.auth.test mocks the seam instead of stubbing location.
- Jest 30 dropped json-summary from default coverageReporters; set them
  explicitly so CI's coverage-summary.json gate keeps working.

Refs #658
- Bump eslint to ^10, typescript-eslint to ^8.61 and
  eslint-plugin-react-hooks to ^7.1 (all support eslint ^10).
- eslint-plugin-react has no eslint-10 release and contributed only a
  no-op 'react/react-in-jsx-scope: off' rule (never enabled by the next
  or recommended configs), so drop it plus the unused eslint-config-next
  dependency. Lint output is unchanged (0 errors, same 3 pre-existing
  exhaustive-deps warnings).

Refs #658
- Bump tailwindcss to ^4, add @tailwindcss/postcss, drop autoprefixer
  (v4 vendors prefixes automatically).
- postcss.config.js uses the @tailwindcss/postcss plugin.
- globals.css: replace @tailwind directives with @import 'tailwindcss'
  and load the existing JS theme via @config (no CSS-first rewrite).
- tailwind.config.ts: darkMode 'class' (v4 dropped the single-element
  array form). Build emits all custom theme utilities, the .dark
  variant, and the typography plugin.
- Apply v4 size-scale utility renames to preserve appearance/a11y:
  outline-none->outline-hidden (forced-colors focus behavior),
  shadow-sm->shadow-xs, rounded-sm->rounded-xs, blur-sm->blur-xs.

Refs #658
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@frankbria, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 hours, 22 minutes, and 16 seconds. Learn how PR review limits work.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 479df311-4a34-4b1e-bc1b-cd1a4d2066f2

📥 Commits

Reviewing files that changed from the base of the PR and between 8152b43 and b0f7692.

⛔ Files ignored due to path filters (1)
  • web-ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (43)
  • .github/workflows/test.yml
  • web-ui/eslint.config.mjs
  • web-ui/jest.config.js
  • web-ui/package.json
  • web-ui/postcss.config.js
  • web-ui/src/__tests__/lib/api.auth.test.ts
  • web-ui/src/__tests__/lib/auth.test.ts
  • web-ui/src/app/costs/page.tsx
  • web-ui/src/app/globals.css
  • web-ui/src/app/proof/page.tsx
  • web-ui/src/components/blockers/BlockerCard.tsx
  • web-ui/src/components/execution/BlockerEvent.tsx
  • web-ui/src/components/layout/PipelineProgressBar.tsx
  • web-ui/src/components/prd/DiscoveryInput.tsx
  • web-ui/src/components/prd/MarkdownEditor.tsx
  • web-ui/src/components/prd/UploadPRDModal.tsx
  • web-ui/src/components/proof/CaptureGlitchModal.tsx
  • web-ui/src/components/proof/GateEvidencePanel.tsx
  • web-ui/src/components/proof/RunHistoryPanel.tsx
  • web-ui/src/components/review/DiffViewer.tsx
  • web-ui/src/components/review/ExportPatchModal.tsx
  • web-ui/src/components/review/PRHistoryPanel.tsx
  • web-ui/src/components/sessions/AgentChatPanel.tsx
  • web-ui/src/components/sessions/SplitPane.tsx
  • web-ui/src/components/tasks/GitHubIssueBadge.tsx
  • web-ui/src/components/tasks/GitHubIssueImportModal.tsx
  • web-ui/src/components/tasks/TaskBoardView.tsx
  • web-ui/src/components/tasks/TaskCard.tsx
  • web-ui/src/components/tasks/TaskDetailModal.tsx
  • web-ui/src/components/tasks/TaskFilters.tsx
  • web-ui/src/components/ui/badge.tsx
  • web-ui/src/components/ui/button.tsx
  • web-ui/src/components/ui/card.tsx
  • web-ui/src/components/ui/checkbox.tsx
  • web-ui/src/components/ui/input.tsx
  • web-ui/src/components/ui/select.tsx
  • web-ui/src/components/ui/tabs.tsx
  • web-ui/src/components/ui/textarea.tsx
  • web-ui/src/components/workspace/WorkspaceSelector.tsx
  • web-ui/src/lib/api.ts
  • web-ui/src/lib/auth.ts
  • web-ui/src/lib/navigation.ts
  • web-ui/tailwind.config.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/658-frontend-major-deps

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

…logout test

- Tailwind v4 has no bare 'shadow' utility (only shadow-xs..2xl), so the
  card/button/badge/checkbox/tabs 'shadow' classes silently rendered no
  shadow. Map them to 'shadow-sm' (v4's equivalent of v3's default
  shadow: 0 1px 3px). Caught by a built-CSS audit, not jsdom/build.
- auth.test.ts: mock the @/lib/navigation seam so logout()'s redirect is
  asserted without jsdom 30's 'Not implemented: navigation' console
  noise (codex review feedback).

Refs #658
@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown

Code Review -- PR 695: Frontend major-dependency upgrades (Jest 30, ESLint 10, Tailwind 4)

Overall: LGTM. Clean, well-scoped upgrade. Each migration follows the correct path and the PR description explains every breaking-change decision clearly.

What is done well:

Navigation seam (src/lib/navigation.ts): The indirection layer for window.location is the right call for jsdom 30. It is SSR-safe, tiny surface area, and makes redirect behavior mockable without brittle DOM hacks. The test rewrite in api.auth.test.ts -- swapping Object.defineProperty(window, location) for jest.mock -- is strictly cleaner and will not break on future jsdom changes.

coverageReporters fix in jest.config.js: Explicitly requesting json-summary is a critical guard. CI coverage threshold gate would silently fail to read coverage/coverage-summary.json without it.

Tailwind v4 renames (complete sweep): Verified no outline-none, shadow-sm, rounded-sm, or blur-sm appear in any added lines. Sweep covers button, input, checkbox, badge, select, textarea, tabs, and page components. The backdrop-blur-sm to backdrop-blur-xs rename in DiffViewer is caught too.

ESLint 10 cleanup: eslint-config-next was never imported in the flat config, and the lint script runs eslint directly (not next lint). Removing it is correct. Dropping eslint-plugin-react is safe -- its react/react-in-jsx-scope:off rule was a no-op under Next.js recommended configs.

darkMode: class (not array): The array form was deprecated in Tailwind v4 with the @config bridge. Fixed correctly.


Minor observations (non-blocking):

  1. api.ts 401 handler -- typeof window guard is still load-bearing. The outer typeof window check is correct because clearToken() calls localStorage.removeItem() which throws server-side. Since redirectTo() is now server-safe internally, a future reader might remove the guard as dead code. A short comment (e.g. clearToken uses localStorage) would make the intent explicit.

  2. @config path is relative to globals.css. The path two levels up correctly resolves to web-ui/tailwind.config.ts. If globals.css ever moves, the path will need updating -- heads-up for future refactors.

  3. @tailwindcss/typography 0.5.19 supports Tailwind v4 (requires >=0.5.14). Confirmed compatible.


Summary: All three upgrades are implemented correctly. The navigation seam is the most architecturally interesting addition and it is solid. Ready to approve once a quick visual sanity check of the Tailwind-renamed components is done.

@frankbria

Copy link
Copy Markdown
Owner Author

Verification & review cycle

Local gates (all green):

  • npm run build ✅ · npm run test:coverage ✅ 1020 tests, 68.91% (gate 65%) · npm run lint ✅ 0 errors (3 pre-existing exhaustive-deps warnings, unchanged from baseline)

Demo (Tailwind v4 — the risky part): built the app, served next start, loaded /login via headless browser. The page renders correctly from the custom @config theme — card border/shadow, themed primary button, muted/accent text, icons, dark-variant CSS all present.

Review cycle (cross-family + CSS audit):

  • A built-CSS audit caught that Tailwind v4 has no bare shadow utility (only shadow-xs…2xl), so card/button/badge/checkbox/tabs were silently rendering no shadow. Fixed by mapping bare shadowshadow-sm (v4's equivalent of v3's 0 1px 3px default). next build succeeds even with unresolved utilities, and jsdom doesn't render CSS — this only surfaces via a CSS audit or visual check.
  • codex review (cross-family) flagged one [P2]: auth.test.ts's logout() hit the real navigation seam → jsdom 30 "Not implemented: navigation" console noise. Fixed by mocking @/lib/navigation there too (now asserts the redirect).
  • CodeRabbit: passed.

Known limitation: CI has no CSS visual-diff. The v4 utility renames here preserve appearance, but a manual visual pass remains the only thing that catches subtle theme shifts.

@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown

Code Review — PR #695: Frontend Major Dependency Upgrades

This is a well-prepared migration PR. The three upgrade areas are clearly separated and the PR description is exemplary — every non-obvious decision has a documented rationale. No prior reviews to reconcile against; this is a first pass.


Overview

Upgrades Jest 29→30, jsdom 29→30, ESLint 9→10, and Tailwind 3→4. The PR is cleanly structured as three logical commits, each independently passing CI. The migration work is correct and the known limitations are honestly documented.


What is done well

navigation.ts seam is the right call for jsdom 30 making window.location non-configurable. Extracting currentPathname/redirectTo provides a proper mock point without the fragile Object.defineProperty gymnastics. The SSR guard (typeof window !== 'undefined') was correctly moved from the callers into the seam itself, keeping the seam clean.

coverageReporters fix in jest.config.js is necessary and correct — Jest 30 dropped json-summary from the defaults and the CI gate reads coverage/coverage-summary.json. Without this, the coverage threshold check would silently stop working.

Tailwind v4 utility renames are consistently applied across all UI components: outline-noneoutline-hidden, shadow-smshadow-xs, rounded-smrounded-xs. Each outline-hidden instance is paired with a visible ring (focus-visible:ring-* or focus:ring-*) so focus accessibility is preserved.

Test updates in api.auth.test.ts and auth.test.ts are clean — the brittle beforeEach/afterEach location stub boilerplate is replaced with simple mock assertions against the navigation seam.


Issues and observations

1. Node.js version precision (low risk)

ESLint 10 transitive deps (@eslint/config-array, @eslint/core, etc.) declare engine requirements of ^20.19.0 || ^22.13.0 || >=24. CI uses node-version: '20' which resolves to the latest Node 20.x — should be ≥20.19.x in practice, so no current blocker. Consider tightening to '20.19' in .github/workflows/test.yml to make the minimum explicit and prevent a regression if the runner ever pins to an older Node 20 patch.

2. eslint-config-next removal — possible silent rule loss

eslint-config-next wraps not just eslint-plugin-react but also eslint-plugin-import and eslint-plugin-jsx-a11y. The PR uses @next/eslint-plugin-next directly, which covers Next.js-specific rules but not those two supplemental plugins. The PR author verified lint output is unchanged (0 errors, 3 pre-existing warnings), so nothing is actively failing — but import ordering and a11y rules may now be silently absent. Worth a follow-up issue rather than a blocker here.

3. outline-hidden on SelectItem without a ring (pre-existing, not introduced here)

SelectItem in select.tsx uses outline-hidden with only focus:bg-accent for focus indication — no ring. This matches the v3 behavior (outline-none + background change), so this PR does not regress accessibility. Noting it only because background-color-only focus indication is the weakest approach for contrast-sensitive users.

4. window.location.href full reload (intentional, noting for future)

redirectTo(path) in navigation.ts triggers a full page reload. For auth boundary redirects (logout, 401) this is deliberate — it guarantees React state is fully torn down. Worth adding a short note in the JSDoc so future callers know to use router.push for soft navigations instead of reaching for this seam.


Minor nits

  • The logout test title was upgraded from 'clears the token' to 'clears the token and redirects to /login' — matches the now-observable behavior correctly.
  • globals.css @config '../../tailwind.config.ts' path resolves to web-ui/tailwind.config.ts relative to web-ui/src/app/globals.css. Correct.
  • darkMode: ['class']darkMode: 'class' in tailwind.config.ts is the required v4 syntax. Correct.

Verdict

No blocking issues. Migration is correct, 1020 tests pass at 68.91% coverage (above the 65% gate), and all CI gates remain intact. The Node engine precision note and the eslint-config-next coverage gap are the main follow-up items.

Approve when CI is green. Once merged, close the superseded Dependabot PRs #639, #640, #641, #642 as noted in the PR description.

ESLint 10's transitive deps require Node ^20.19 || ^22.13 || >=24. The
floating '20' tag currently resolves to a new enough patch, but pinning
the minimum makes the requirement explicit and regression-proof.

Refs #658
@frankbria

Copy link
Copy Markdown
Owner Author

Thanks — addressed the review notes:

  • Node engine floor (ESLint 10 deps need ≥20.19): pinned NODE_VERSION from the floating '20' to '20.19' in test.yml (b0f7692), making the minimum explicit and regression-proof.
  • eslint-config-next supplemental plugins (import, jsx-a11y): not a regression — the flat eslint.config.mjs never imported eslint-config-next; it wires @next/eslint-plugin-next directly. Those plugins were already absent before this PR, so removing the unused dep changes nothing. Adding jsx-a11y is a reasonable future enhancement, out of scope here.
  • select.tsx outline-hidden + background-only focus: agreed it matches prior v3 behavior (no regression); leaving as-is.

CI is re-running on the pin; will merge once green.

@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown

Code Review — PR 695 Follow-up (post NODE_VERSION pin)

Reviewing the latest commit (b0f7692) and confirming disposition of all prior open items.

NODE_VERSION pin — correct

NODE_VERSION: '20.19' in test.yml is the right fix. The ESLint 10 dep tree (@eslint/config-array, @eslint/core, etc.) declares node: '^20.19.0 || ^22.13.0 || >=24' — a floating '20' tag resolves to whatever the runner images have cached and could silently land on a 20.18.x build. The pin is minimal (only locks the patch floor, not a full version triple) and will automatically ride forward with npm's SemVer resolution as runner images update.

Prior open items — resolved

  • Node engine floor for ESLint 10: Fixed — 20.19 pinned
  • eslint-config-next supplemental plugin gap (import, jsx-a11y): Confirmed pre-existing — flat config never imported it; no regression
  • outline-hidden on SelectItem without ring: Confirmed pre-existing v3 behavior — acceptable as-is
  • Bare shadow utility (CSS audit; Tailwind v4 has no bare shadow utility): Fixed in earlier commit — badge/button/card/checkbox/tabs all updated to shadow-sm
  • auth.test.ts navigation seam mock: Fixed — redirectTo is mocked; no more jsdom console noise

Nothing new was introduced in the NODE_VERSION commit.

Final verdict

All review items are closed. The three upgrade areas (Jest 30, ESLint 10, Tailwind 4) are correctly implemented, 1020 tests pass at 68.91% coverage (above the 65% gate), and CI gates remain intact.

Ready to merge. Close superseded Dependabot PRs 639, 640, 641, 642 once landed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P6.8.2] Frontend major-dependency upgrades: jest 30, jsdom 30, eslint 10, tailwind 4

1 participant