Skip to content

Oxc migration#1879

Open
thibaultleouay wants to merge 15 commits intomainfrom
oxc-migration
Open

Oxc migration#1879
thibaultleouay wants to merge 15 commits intomainfrom
oxc-migration

Conversation

@thibaultleouay
Copy link
Copy Markdown
Member

No description provided.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 24, 2026

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

Project Deployment Actions Updated (UTC)
openstatus-dashboard Ready Ready Preview, Comment Feb 26, 2026 9:55am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
openstatus-status-page Skipped Skipped Feb 26, 2026 9:55am
openstatus-web Skipped Skipped Feb 26, 2026 9:55am

Request Review

@thibaultleouay
Copy link
Copy Markdown
Member Author

@claude review this

@claude
Copy link
Copy Markdown

claude bot commented Feb 25, 2026

Claude finished @thibaultleouay's task in 3m 37s —— View job


PR Review: Oxc Migration

  • Gather context
  • Analyze key configuration changes (oxlint, oxfmt)
  • Review GitHub workflow changes
  • Review app-level changes
  • Check formatting consistency
  • Provide final review summary

Summary

This PR migrates the entire monorepo from Biome to oxlint (linting) + oxfmt (formatting). The scope is massive — 1167 files changed — but the vast majority of changes are purely mechanical: import reordering, Tailwind class sorting, and quote/indentation normalization. A few substantive issues are worth reviewing.


Configuration Changes

.oxfmtrc.json (new file) — well-structured config:

  • experimentalSortImports groups: type-import → value-builtin → value-external → value-internal
  • experimentalTailwindcss enabled for cn and clsx functions (causes Tailwind class reordering in JSX)

.oxlintrc.json — expanded significantly with multiple plugins: react, typescript, unicorn, jsx-a11y, oxc, nextjs, import.

package.json (root) — clean dependency swap:

  • Removed @biomejs/biome@1.8.3 and oxlint-tsgolint
  • Updated oxfmt: 0.20.0 → 0.35.0, oxlint: 1.36.0 → 1.50.0
  • New scripts: lint:type, format:check replacing old biome-specific variants

Issues Found

🔴 Type Error in packages/tracker/src/tracker.ts:84

The isOngoingReport() method was refactored to use a Set (a good performance improvement) but the type annotation was not updated:

// Current (wrong type annotation):
const resolved: StatusReport["status"][] = new Set([
  "monitoring",
  "resolved",
]);

// Should be:
const resolved = new Set<StatusReport["status"]>(["monitoring", "resolved"]);

new Set(...) is not assignable to StatusReport["status"][]. If TypeScript strict mode is enabled this will be a type error. The .has() method usage is correct and the perf improvement is good — the type annotation just needs fixing. Fix this →

🟡 oxfmt Adds Spurious Parentheses Around Array Literals

In apps/dashboard/sentry.server.config.ts:

// Formatted result (odd but harmless):
const IGNORED_TRPC_CODES: TRPCError["code"][] = ([
  "UNAUTHORIZED",
  "NOT_FOUND",
  "BAD_REQUEST",
]);

oxfmt is wrapping the array in parentheses when there's a generic type annotation. This is semantically identical but looks like a formatter quirk/bug. Worth reporting to the oxfmt project if this bothers you.

🟡 Test Files No Longer Explicitly Ignored in oxlint

The old .oxlintrc.json had "**/*.test.ts" in ignorePatterns. The new config dropped that pattern. Test files will now be linted by oxlint. This may cause new lint warnings/errors in test files (especially with unicorn and typescript plugins enabled).

🟡 Biome-ignore Suppressions Removed (89 instances)

89 biome-ignore comments were stripped. The most notable categories:

Removed suppression oxlint equivalent?
lint/correctness/useExhaustiveDependencies Not in oxlint — these hooks may silently have incomplete deps now
lint/suspicious/noExplicitAny Partially: typescript/no-explicit-any exists but is not enabled
lint/performance/noAccumulatingSpread Yes: oxc/no-accumulating-spread is configured

The useExhaustiveDependencies suppressions in sidebar.tsx and monitors/list/client.tsx are worth double-checking — the hooks were suppressed for a reason.


Positive Observations

  • toSorted() instead of .sort() in packages/tracker/src/tracker.ts:158 — uses the immutable sort method, correct improvement
  • Set.has() instead of Array.includes() — O(1) lookup is better (just needs the type fix above)
  • Biome completely removed — single source of truth for formatting/linting
  • YAML workflow files cleaned up (indentation, quote consistency) — makes them more readable
  • VS Code settings updated to use nicolo-ribaudo.vscode-oxfmt formatter
  • CLAUDE.md updated to reflect the new toolchain
  • Import grouping is now explicit and consistent across the codebase

Minor Observations

  • packages/utils/index.ts: An empty line between imports was removed — harmless but worth noting as it changes import readability slightly
  • ralph/README.md: Missing newline at end of file added — good
  • CSS values normalized (0.700.7, 0.200.2) by oxfmt — correct CSS style

Verdict

The migration is well-executed for its scale. The one actionable item before merging is the type annotation bug in tracker.ts. Everything else is either a formatter quirk or a minor consideration worth tracking.

@thibaultleouay
Copy link
Copy Markdown
Member Author

@claude review this again

@claude
Copy link
Copy Markdown

claude bot commented Feb 25, 2026

Claude finished @thibaultleouay's task in 4m 32s —— View job


PR Review: Oxc Migration (Re-review)

  • Gather context
  • Analyze diff against main branch
  • Check key configuration files
  • Review source code changes
  • Check for previously identified issues
  • Provide final review summary

Summary

This PR migrates the entire monorepo from Biome to oxlint@1.50.0 + oxfmt@0.35.0. 1,167 files changed, but the vast majority are mechanical: import reordering, Tailwind class sorting, indentation normalization (tabs→spaces in packages/ui), and quote normalization.

Correction from previous review: The tracker.ts "Set type error" identified previously was incorrect — the code always used an array with .includes(), never a Set. The change in this file is purely cosmetic (multiline array formatting + import reordering). No bug there.


Configuration Analysis

.oxfmtrc.json (new):

  • experimentalSortImports: internal imports sorted after external (causes the bulk of import reordering noise)
  • experimentalTailwindcss.functions: ["cn", "clsx"]: drives Tailwind class reordering in all JSX files

.oxlintrc.json (expanded):

  • Plugins added: react, typescript, unicorn, jsx-a11y, oxc, nextjs, import
  • Categories: correctness/suspicious/perf all set to warn
  • **/*.test.ts was previously in ignorePatterns — now removed. Test files will be linted by the new config.

package.json (root):

  • biome@1.8.3 and oxlint-tsgolint removed
  • oxfmt: 0.20.00.35.0, oxlint: 1.36.01.50.0
  • Scripts updated: lint, lint:fix, lint:type, format, format:check

Issues Found

🟡 dashboard/package.json: next lint Replaced by oxlint .

apps/dashboard/package.json:7: The dashboard app's per-package lint script changed from next lint to oxlint .. next lint runs ESLint with the full Next.js rule set (including @next/next rules for image optimization, link component usage, etc.). While .oxlintrc.json includes the nextjs oxlint plugin, it may not be a full equivalence — worth verifying the nextjs oxlint plugin covers the rules you relied on. apps/web/package.json and apps/status-page/package.json have the same change.

🟡 oxfmt Adds Parentheses Around Ternary Branches

Two instances of the formatter adding semantically-no-op parentheses:

// apps/dashboard/sentry.server.config.ts
const IGNORED_TRPC_CODES: TRPCError["code"][] = ([
  "UNAUTHORIZED",
  "NOT_FOUND",
  "BAD_REQUEST",
]);
// apps/dashboard/src/app/(dashboard)/monitors/(list)/client.tsx
? (globalHttpMetrics?.data?.find(
    (m) => m.monitorId === monitor.id.toString(),
  ) ?? false)
: (globalTcpMetrics?.data?.find(
    (m) => m.monitorId === monitor.id.toString(),
  ) ?? false),

Both are semantically identical to the original. This appears to be an oxfmt quirk when formatting type-annotated array literals and ?? in ternaries. Worth filing a bug with the oxfmt project if these bother you.

🟡 Removed biome-ignore Suppressions — Summary

89 biome-ignore comments were removed. The impactful ones by category:

Suppression removed File Status
useExhaustiveDependencies packages/ui/src/hooks/use-cookie-state.ts:26 Intentional empty-deps patternuseEffect(fn, []) runs once on mount to read cookie. oxlint's react plugin doesn't enforce exhaustive-deps by default, so no regression
useExhaustiveDependencies packages/ui/src/components/ui/sidebar.tsx Dependencies in both hooks are actually complete — removal is correct
noExplicitAny Various server/dashboard files typescript/no-explicit-any rule exists in oxlint but is not enabled in .oxlintrc.json, so these any usages will silently pass
noAccumulatingSpread apps/server/src/routes/v1/monitors/post.ts + 4 other routes oxc/no-accumulating-spread is configured as warn — these .reduce((a, v) => ({ ...a, [v.key]: v.value }), {}) patterns will now trigger lint warnings
noDangerouslySetInnerHtml apps/web/src/app/(landing)/** All usages are JSON-LD structured data injection — safe, no rule equivalent in oxlint config
noUnusedVariables packages/tinybird/src/audit-log/examples.ts seed() and history() functions are now unguarded — eslint/no-unused-vars is set to warn in the new config, so these will generate warnings

🟢 No-Op Change: tracker.ts

Previous review incorrectly flagged a type error here. The isOngoingReport() method uses a typed array with .includes() — always correct. The diff is purely formatting (multiline array, import reorder). ✅


Positive Observations

  • Complete toolchain consolidation — single source of truth for formatting/linting
  • experimentalTailwindcss sorting on cn/clsx — consistent class ordering across the codebase
  • noAccumulatingSpread now warned on for the reduce patterns in server routes — these are real perf concerns worth addressing in a follow-up
  • VS Code settings properly updated to nicolo-ribaudo.vscode-oxfmt
  • CLAUDE.md updated to reflect the new toolchain conventions
  • YAML workflows normalized (quote style, indentation) — makes them more diffable

Verdict

Migration is well-executed. No blocking issues. Two things worth a follow-up after merging:

  1. Verify nextjs oxlint plugin coverage vs. the next lint rules you were running before — especially for the dashboard app where framework-specific lint rules matter most.
  2. Address noAccumulatingSpread warnings in the Hono server routes (5 files) — replace the .reduce((a, v) => ({...a, ...v})) pattern with Object.fromEntries() or Object.assign() for better performance.

@thibaultleouay
Copy link
Copy Markdown
Member Author

@claude review it again

@claude
Copy link
Copy Markdown

claude bot commented Feb 25, 2026

Claude finished @thibaultleouay's task in 4m 13s —— View job


PR Review: Oxc Migration (Third Pass)

  • Gather context
  • Analyze configuration changes
  • Review substantive code changes
  • Check for new issues
  • Provide final summary

What This PR Does

Migrates the entire monorepo from @biomejs/biome@1.8.3 to oxfmt@0.35.0 + oxlint@1.50.0. The vast majority of the 18k additions / 23k deletions are mechanical: import reordering, Tailwind class sorting (via experimentalTailwindcss), and indentation normalization in YAML/JSON files.


Issues Found

🟡 ignorePatterns in .oxlintrc.json uses single-glob for UI components

"packages/ui/src/components/*.tsx",
"packages/ui/src/components/*.ts",

The * in a path segment matches filenames in that directory only — not subdirectories. So packages/ui/src/components/ui/sidebar.tsx, packages/ui/src/components/ui/button.tsx, packages/ui/src/components/blocks/*.tsx, etc. are all still linted despite the intent appearing to exclude shadcn components.

If the goal is to exclude all generated/shadcn UI components, change to:

"packages/ui/src/components/**/*.tsx",
"packages/ui/src/components/**/*.ts",

If you intentionally only want to exclude files directly in components/ (not in ui/ or blocks/ subdirs), the current pattern is correct — but it's non-obvious. Fix this →

🟡 packages/proto/gen reformatted by oxfmt but no oxfmt ignore pattern

.oxlintrc.json correctly excludes packages/proto/gen from linting. But .oxfmtrc.json has no ignore patterns — so all proto-generated *_pb.ts files get reformatted by oxfmt.

The consequence: every time you run buf generate to regenerate protos, the output files will have different formatting from what's committed, requiring a pnpm format pass before committing. If oxfmt supports an ignorePatterns / exclude field (check the schema), add it. Otherwise, add a post-generate step to packages/proto/scripts/clean-openapi.ts or a comment at the top of generated files.

🟡 format script now also runs oxlint --fix

package.json root:

"format": "oxfmt --write . && oxlint --fix .",

oxlint --fix can make semantic code changes (e.g., applying no-accumulating-spread fixes), not just whitespace. Mixing this into format is unexpected — developers typically expect pnpm format to only touch style, not code semantics. Consider separating: keep format for oxfmt only and lint:fix for the linter.

🟡 tinybird/src/audit-log/examples.ts will now emit lint warnings

The two biome-ignore lint/correctness/noUnusedVariables comments were removed from seed() and history(), and eslint/no-unused-vars: "warn" is now active in .oxlintrc.json. Unless these functions are called somewhere else (they aren't in this file), they'll generate warnings on every lint run.

Either add an oxlint-specific disable comment or actually use/export these functions. Fix this →

🟡 **/*.test.ts dropped from ignorePatterns

Previous ignorePatterns: ["**/*.test.ts", "**/*_pb.ts"] — the .test.ts glob was silently removed. Test files will now be linted with the full unicorn, typescript, react etc. plugin stack. This could surface warnings. Not a blocker, just worth verifying with a pnpm lint run after merge.


Things Verified Correct (Previous Reviews Had Errors)

sidebar.tsxbiome-ignore removals are safe

toggleSidebar deps: [isMobile, setOpen, setOpenMobile] — complete.
contextValue deps: [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] — complete.
The biome suppressions were masking false positives; removing them is correct.

tracker.ts — no code change

The isOngoingReport() method still uses a typed array with .includes(). The only change is the array being formatted across multiple lines. No logic change.

deploy.yml YAML reformatting — semantically equivalent

# Old (block scalar):
- run:
    flyctl deploy --config apps/server/fly.toml
    --dockerfile apps/server/Dockerfile ...

# New (inline with continuation):
- run: flyctl deploy --config apps/server/fly.toml
    --dockerfile apps/server/Dockerfile ...

Both parse to the same multi-line plain scalar in YAML. The indentation of the continuation (--dockerfile...) is greater than the mapping indicator, so it's a valid YAML continuation. Safe.

use-cookie-state.ts — intentional empty-deps pattern

useEffect(() => { ... read cookie ... }, []) runs once on mount — intentional. No exhaustive-deps rule is enabled in oxlint by default so no regression.


Minor Observations

  • CSS numeric normalization: 0.700.7, 0.200.2 in globals.css — valid CSS, no visual change.
  • monitors/[id]/sidebar.tsx Tailwind class reordering: purely cosmetic, e.g. "h-2.5 w-2.5 text-muted-foreground""text-muted-foreground h-2.5 w-2.5". No visual change.
  • .gitignore: Added .zed (Zed editor config) — good.
  • oxfmt adds parentheses around ternary ?? branches in monitors/(list)/client.tsx — semantically identical, appears to be an oxfmt quirk worth filing upstream.
  • Root package.json: name, workspaces, packageManager fields reordered by formatter — no functional change.

Verdict

No blockers. The migration is solid. Three actionable follow-ups worth addressing before or shortly after merge:

  1. Clarify the packages/ui/src/components/*.tsx glob intent (single * vs **/*)
  2. Add a note/step for re-formatting proto gen files after regeneration
  3. Separate format (oxfmt-only) from lint:fix (oxlint --fix) in the root scripts

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Feb 25, 2026

Deploying openstatus-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 03fc051
Status: ✅  Deploy successful!
Preview URL: https://622f8a56.openstatus-1sy.pages.dev
Branch Preview URL: https://oxc-migration.openstatus-1sy.pages.dev

View logs

@thibaultleouay
Copy link
Copy Markdown
Member Author

@claude review this again

@claude
Copy link
Copy Markdown

claude bot commented Feb 25, 2026

Claude finished @thibaultleouay's task in 3m 3s —— View job


PR Review: Oxc Migration (Fourth Pass)

  • Gather context and analyze diff
  • Review configuration changes
  • Review workflow/CI changes
  • Review app-level changes
  • Check for new issues since last review
  • Provide final summary

Summary

Migrates the full monorepo from @biomejs/biome@1.8.3 to oxfmt@0.35.0 + oxlint@1.50.0. The bulk of the diff (1,190 files) is mechanical: import reordering, Tailwind class sorting, quote normalization. Three actionable issues identified this pass.


🔴 Type Error: Set Assigned to Array Type (7 files)

This is a genuine TypeScript error that will fail tsc --noEmit with strict: true. The pattern appears in 7 files:

apps/dashboard/sentry.server.config.ts:9, sentry.edge.config.ts:9
apps/status-page/sentry.server.config.ts:9, sentry.edge.config.ts:9
apps/web/sentry.server.config.ts:11, sentry.edge.config.ts:9
packages/tracker/src/tracker.ts:84

// Wrong — type annotation says array, value is Set
const IGNORED_TRPC_CODES: TRPCError["code"][] = new Set([
  "UNAUTHORIZED",
  "NOT_FOUND",
  "BAD_REQUEST",
]);
IGNORED_TRPC_CODES.has(...) // TS error: Property 'has' does not exist on type '...[]'
// Fix — use Set type annotation or drop annotation and let TS infer
const IGNORED_TRPC_CODES = new Set<TRPCError["code"]>([
  "UNAUTHORIZED",
  "NOT_FOUND",
  "BAD_REQUEST",
]);

Same fix needed in tracker.ts:84 where StatusReport["status"][] is used as the annotation for a new Set(...). Fix all 7 instances →


🟡 noAccumulatingSpread Warnings Now Active (5 server route files)

biome-ignore lint/performance/noAccumulatingSpread comments were removed from 5 server route files, and oxc/no-accumulating-spread: "warn" is now active. The pattern:

// apps/server/src/routes/v1/monitors/get.ts, get_all.ts, post.ts, post_dns.ts, post_http.ts
.reduce((a, v) => ({ ...a, [v.key]: v.value }), {})

This will emit lint warnings on every run. The clean fix is:

Object.fromEntries(parsed.map((v) => [v.key, v.value]))

Not a blocker, but these will clutter lint output. Worth a follow-up PR. Fix this →


🟡 Proto Gen Files Reformatted by oxfmt (22 files)

.oxlintrc.json correctly excludes packages/proto/gen from linting, but .oxfmtrc.json has no ignore patterns — so pnpm format rewrites all 22 generated protobuf TypeScript files. The 22 _pb.ts files and openapi.yaml in packages/proto/gen/ are now committed with oxfmt-normalized quotes and spacing.

Consequence: every time buf generate is re-run, the output will differ from what's committed until pnpm format is run again — confusing diffs and an extra step for proto regeneration.

Consider adding an exclude to .oxfmtrc.json (check if the format supports it) or add a pnpm format step to the proto generation script. Fix this →


🟡 ignorePatterns Glob Misses Subdirectories for UI Components

.oxlintrc.json:

"packages/ui/src/components/*.tsx",
"packages/ui/src/components/*.ts",

The single * only matches files in components/ itself — not components/ui/, components/blocks/, etc. If the intent is to exclude all shadcn/generated UI components, it should be **/*.tsx / **/*.ts. As-is, components/ui/sidebar.tsx, components/blocks/status-banner.tsx, and all other subdirectory files are still linted. Fix this →


🟡 format Script Mixes Formatting and Lint Fixes

"format": "oxfmt --write . && oxlint --fix .",

oxlint --fix applies semantic code changes (e.g., replacing accumulating spreads), not just whitespace. Developers expect format to be safe/idempotent. This is a minor footgun — consider splitting: format = oxfmt only, lint:fix = oxlint --fix.


Things Confirmed Correct

Item Status
deploy.yml YAML multi-line scalar reformatting ✅ Semantically equivalent
sidebar.tsx biome-ignore useExhaustiveDependencies removals ✅ All deps were already complete
use-cookie-state.ts empty-deps useEffect ✅ Intentional mount-only pattern, no regression
toSorted() usage in tracker.ts:155 ✅ Correct immutable sort
oxlint-tsgolint updated 0.10.1^0.15.0 ✅ Good version bump
Proto gen files linting excluded via ignorePatterns ✅ Correct in oxlintrc

Verdict

One blocking issue before merge: the Set type annotation error in 7 files will fail tsc --noEmit (strict mode is enabled). Everything else is either a follow-up item or minor observation.

mxkaske and others added 4 commits February 25, 2026 22:07
* chore: dashboard agents page

* fix: test

* feat: slack agent content for status-page product page
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.

2 participants