Skip to content

build(frontend): lint and format with Biome and check the frontend in CI - #117

Merged
klin2686 merged 8 commits into
mainfrom
refactor/frontend-biome
Oct 14, 2026
Merged

klin2686 merged 8 commits into
mainfrom
refactor/frontend-biome

Conversation

@klin2686

@klin2686 klin2686 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  1. Replaces eslint-config-next with Biome for linting and formatting; ESLint stays only for the React Compiler rules Biome doesn't have.
  2. Reformats the frontend with Biome and fixes every Biome and React Compiler finding.
  3. Adds a Frontend CI workflow and runs Biome on staged frontend files in the pre-commit hook.
  4. Fixes two member-management bugs found while testing.

What changed

Frontend

Tooling

  • Adds @biomejs/biome 2.5.15 (exact pin) with frontend/biome.jsonc: the recommended preset plus the React and Next.js domains (enabled from dependencies), 2-space indent, lineWidth 100, double quotes and semicolons.
  • Turns off noNonNullAssertion (! is how the code narrows) and useExhaustiveDependencies's unnecessary-dependency reports (an extra dependency is a legitimate re-run trigger).
  • Turns off noStaticElementInteractions and useKeyWithClickEvents for now: keyboard support for clickable non-buttons is left to an accessibility pass.
  • Treats Checkbox and Switch as inputs for noLabelWithoutControl, since both render a labelable element.
  • Excludes **/sheets/** (the deprecated Google Sheets import) from Biome and ESLint linting; those files are still formatted.
  • eslint.config.mjs now runs only eslint-plugin-react-hooks's recommended preset, pinned to 7.0.1 (the version eslint-config-next bundled), with rules-of-hooks and exhaustive-deps off because Biome covers them.
  • New scripts: check, fix (biome check --write), format, lint (Biome), lint:react (ESLint), typecheck.
  • Pins packageManager to pnpm 10.34.6, the newest version Vercel supports, and replaces ignoredBuiltDependencies with allowBuilds in pnpm-workspace.yaml, which pnpm 11+ requires.
  • Adds a root biome.jsonc that covers frontend/** and .vscode/*.json (lineWidth 100) and owns .gitignore handling, so editors opened at the repo root find Biome; frontend/biome.jsonc is nested under it with "root": false.

Formatting

  • Formats every frontend file with Biome and applies its safe fixes: import type for type-only imports and sorted imports. Listed in .git-blame-ignore-revs. (00171979)
  • .gitattributes checks out frontend/**, the root biome.jsonc and .vscode/*.json with LF endings, so Windows checkouts with core.autocrlf match Biome's output.

Lint fixes

  • useOptionalChain fixes written by hand on the forms, members and invites pages and in lib/exports, keeping the null/false results Biome's own fix would have turned into undefined.
  • forEach callbacks that returned values now use for...of (FormFillFlow, MassEventEditor, MassRoleEditor, MasonryGrid, usePanelSelection).
  • aria-hidden on the decorative SVGs in Icons.tsx and ProgressRing, <title> in public/favicon.svg, and type="button" on five navigation and menu buttons outside forms.
  • ChipInput's caption is now a <label htmlFor> tied to its text input; the captions in ProfileQuestion and NewTournamentModal's Division field are <div>s, since they caption groups rather than one input.
  • The panel effects on the members page and the assignments board also re-run when the tournament's collect_is_over_18/collect_is_over_21 flags change, as their comments say they should.
  • lib/sorting.ts: parameter valueOf renamed to valueFor, which no longer shadows Object.prototype.valueOf.
  • Remaining intentional cases are suppressed with a reason, including the 20 react-hooks/exhaustive-deps disables converted to Biome suppressions.

React Compiler fixes

  • New lib/useIsClient.ts (useSyncExternalStore) replaces the mounted-flag effects in Modal, ExportModal and ToastProvider; useIsMobile now uses useSyncExternalStore over matchMedia.
  • State adjusted during render instead of in effects: TracksSection, Dropdown, FormPopover, Tooltip, FieldKeyPopover, PresetPopover, and the assignments board's per-tab filters and sort and its filter matches, which clear when the filters do.
  • Derived instead of stored: the join page's missing-code error, the member page's access error, and useMyMembership's loading.
  • Textarea's expand and collapse moved into its focus, blur and mouse-leave handlers.
  • The dashboard captures the load time with useState for banner snoozes, and snoozedUntil moves to module scope.
  • useExitAnimation skips its closing ghost while the element is still in the page, which StrictMode's simulated unmount produces now that Modal renders its portal on the first render.
  • FormPopover and Tooltip each keep one set-state-in-effect suppression for state set from a DOM measurement in a layout effect.

Bug fixes

  • The bulk remove dialog accepts REMOVE: its caption displays in uppercase, but the check required lowercase remove. (fa7b9d55)
  • The member page (/dashboard/tournaments/[id]/members/[membershipId]) re-reads after its assignments section saves; reloadKey was bumped but never read. (a7d84083)

CI and hooks

  • New .github/workflows/frontend.yml ("Frontend CI"): biome ci, pnpm lint:react and pnpm typecheck on PRs that touch frontend/, the root biome.jsonc or the workflow. No next build, since Vercel builds every PR.
  • .githooks/pre-commit runs its backend and frontend checks independently; the frontend side runs Biome on staged files through lint-staged, which leaves unstaged changes untouched. The backend checks run in a subshell, so their cd backend can't hide staged frontend files from the frontend check.

Editor

  • .vscode/settings.json points the Biome extension at frontend/node_modules through biome.lsp.bin, with publicHoistPattern in pnpm-workspace.yaml putting the binary at an unversioned path.
  • Biome is the VS Code formatter for JS, TS, JSON and CSS, and eslint.workingDirectories points the ESLint extension at frontend/; both extensions are recommended.
  • CONTRIBUTING.md and frontend/CLAUDE.md describe the new commands, the hook, the editor setup and how to suppress rules.

Out of scope

  • Upgrading eslint-plugin-react-hooks to 7.1, which reports 44 more React Compiler findings, including the new refs rule.
  • Keyboard support for clickable non-buttons, RadioList, OptionList options and the Dropdown footer.
  • A <button> nested in a <button> in the form actions menu's "Allow response edits" row (FormActionsMenu puts a Switch inside a Popover item).
  • A 308 redirect on every proxied API call: lib/api.ts requests a trailing slash that Next.js strips and the proxy route adds back.
  • Fixing or removing the Google Sheets import.
  • A backend bug in PUT /forms/{form_id}/fields/: renaming one question away from a key and giving that key to another in the same save returns 409, because bulk_update_fields checks each key against the database while earlier renames in the request are unflushed (autoflush=False).

Test plan

  • Clicked through the UI change in a browser (partly; see Manual)

Automated

  • pnpm check, pnpm lint:react, pnpm typecheck and pnpm build pass.
  • Pre-commit hook: a staged frontend file is formatted and restaged, a partially staged file keeps its unstaged lines, a Biome error blocks the commit, the backend checks still run on backend changes, and a commit staging both backend and frontend files runs both.

Manual

  • Sign-in and sign-up links, the back link, the avatar menu and log out work.
  • /verify-email?token=bogus and /confirm-email-change?token=bogus show the error state once.
  • Sidebar labels show on hover and stay visible on Settings pages.
  • Members page: member panel, role edits, mass role editor, selection, and bulk remove with REMOVE.
  • New tournament modal fades in and out without a jump; Esc closes it with the closing animation.
  • A searchable dropdown reopens with its search cleared and the current value highlighted.
  • Assignments board: member panel opens, and each track tab keeps its own filters and sort.
  • Events: ticking two events opens the mass editor.
  • Settings → General: Add track auto-expands the new row, which stays collapsed once closed; clicking the staff invite modal's Emails caption focuses its input.
  • Form builder: a duplicate key opens the key popover with the error on Save, and editing the key clears it.
  • /join with no code shows the invalid-invite error.
  • A non-manager opening another member's page at /dashboard/tournaments/[id]/members/[membershipId] sees "No access".
  • Not clicked through: the forms list menu, form branching preview, invites, the audit log, exports, Textarea expansion, status tooltips, clearing and re-setting the assignments board's member filters, and the mobile layout; those rely on the type check, lint and production build.

@railway-app

railway-app Bot commented Oct 14, 2026

Copy link
Copy Markdown

This PR was not deployed automatically as @klin2686 does not have access to the Railway project.

In order to get automatic PR deploys, please add @klin2686 to your workspace on Railway.

@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nexus Ready Ready Preview Oct 14, 2026 8:11am UTC

@klin2686
klin2686 force-pushed the refactor/frontend-biome branch from 8b94a95 to 538edfb Compare October 14, 2026 08:10
@klin2686
klin2686 merged commit c2428e7 into main Oct 14, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 538edfb4 Deployed Oct 14, 2026 by vercel[bot]
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.

1 participant