chore(quality): ratchet Oxlint, React Doctor, and Zustand performance (#11034)

* chore(quality): ratchet lint and Zustand performance

* fix(ci): stabilize React peer lock snapshot

* fix(ci): isolate PR diff and React Doctor CLI
This commit is contained in:
Neil 2026-07-27 18:58:36 -07:00 committed by GitHub
parent 9a8e21a47e
commit 12ef12c55b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1324 additions and 143 deletions

View File

@ -24,6 +24,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
@ -34,6 +35,15 @@ jobs:
- name: Check switch exhaustiveness
run: pnpm run lint:switch-exhaustiveness
- name: Enforce changed-code quality
run: pnpm run check:code-quality:changed -- "${{ github.event.pull_request.base.sha }}"
- name: Enforce React Doctor on changed lines
run: pnpm run check:react-doctor:changed -- "${{ github.event.pull_request.base.sha }}"
- name: Check Zustand selector fan-out budget
run: pnpm run check:zustand-selector-fanout
- name: Check styled scrollbars
run: pnpm check:styled-scrollbars

View File

@ -1,6 +1,12 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "react", "react-hooks", "react-perf", "unicorn"],
"jsPlugins": [
{
"name": "mobile-pairing",
"specifier": "./config/oxlint-plugins/mobile-pairing-qrcode-import.mjs"
}
],
"categories": {
"correctness": "error"
},
@ -57,15 +63,11 @@
"name": "@linear/sdk",
"allowTypeImports": true,
"message": "Value-importing @linear/sdk hoists its ~2.6MB CJS bundle into the eager top-level require block and defeats the lazy loader. Use `import type` plus loadLinearSdk() from src/main/linear/linear-sdk.ts."
},
{
"name": "qrcode",
"allowTypeImports": true,
"message": "qrcode is only reachable from mobile pairing; a static import parses it at launch for everyone. Use `import type` plus `await import('qrcode')` at the call site."
}
]
}
],
"mobile-pairing/no-eager-qrcode-import": "error",
"no-useless-return": "error",
"prefer-template": "error",
"unicorn/consistent-empty-array-spread": "error",

View File

@ -0,0 +1,22 @@
{
"$schema": "../node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off",
"suspicious": "off",
"pedantic": "off",
"perf": "off",
"style": "off",
"restriction": "off",
"nursery": "off"
},
"rules": {
"typescript/await-thenable": "warn",
"typescript/no-floating-promises": "warn",
"typescript/no-misused-promises": "warn",
"typescript/only-throw-error": "warn",
"typescript/restrict-plus-operands": "warn",
"typescript/restrict-template-expressions": "warn"
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/out"]
}

View File

@ -0,0 +1,71 @@
{
"$schema": "../node_modules/oxlint/configuration_schema.json",
"plugins": ["import", "jsx-a11y", "promise", "react", "react-hooks", "typescript", "vitest"],
"categories": {
"correctness": "off",
"suspicious": "off",
"pedantic": "off",
"perf": "off",
"style": "off",
"restriction": "off",
"nursery": "off"
},
"options": {
"reportUnusedDisableDirectives": "warn"
},
"jsPlugins": [
{
"name": "app-store-performance",
"specifier": "./oxlint-plugins/app-store-performance.mjs"
}
],
"rules": {
"app-store-performance/require-selector": "warn",
"app-store-performance/no-identity-selector": "warn",
"app-store-performance/no-fresh-selector-result": "warn",
"import/export": "warn",
"import/named": "warn",
"import/namespace": "warn",
"import/no-cycle": ["warn", { "maxDepth": 3 }],
"import/no-duplicates": "warn",
"import/no-self-import": "warn",
"max-params": ["warn", 5],
"no-fallthrough": "warn",
"no-loop-func": "warn",
"no-promise-executor-return": "warn",
"no-unmodified-loop-condition": "warn",
"preserve-caught-error": "warn",
"promise/no-multiple-resolved": "warn",
"react/no-unstable-nested-components": "warn",
"react/react-compiler": "warn"
},
"overrides": [
{
"files": ["src/renderer/src/**/*.{ts,tsx}"],
"rules": {
"jsx-a11y/alt-text": "warn",
"jsx-a11y/anchor-has-content": "warn",
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-role": "warn",
"jsx-a11y/click-events-have-key-events": "warn"
}
},
{
"files": ["**/*.{test,spec}.{ts,tsx}", "tests/**/*.{ts,tsx}"],
"rules": {
"vitest/no-conditional-tests": "warn",
"vitest/no-focused-tests": "warn",
"vitest/no-identical-title": "warn",
"vitest/valid-expect": "warn",
"vitest/valid-title": "warn"
}
},
{
"files": ["mobile/**/*.{ts,tsx}"],
"rules": {
"react-hooks/exhaustive-deps": "warn"
}
}
],
"ignorePatterns": ["**/node_modules", "**/dist", "**/out"]
}

View File

@ -0,0 +1,244 @@
const ALLOCATING_METHODS = new Set([
'filter',
'flat',
'flatMap',
'map',
'toReversed',
'toSorted',
'toSpliced',
'with'
])
function identifierName(node) {
return node?.type === 'Identifier' ? node.name : null
}
function propertyName(node) {
if (node?.type !== 'MemberExpression') {
return null
}
if (!node.computed) {
return identifierName(node.property)
}
return node.property?.type === 'Literal' && typeof node.property.value === 'string'
? node.property.value
: null
}
function returnedExpressions(selector) {
if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
return []
}
if (selector.body.type !== 'BlockStatement') {
return [selector.body]
}
const expressions = []
const visit = (node) => {
if (!node || typeof node !== 'object') {
return
}
if (
node !== selector.body &&
['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node.type)
) {
return
}
if (node.type === 'ReturnStatement') {
if (node.argument) {
expressions.push(node.argument)
}
return
}
for (const [key, child] of Object.entries(node)) {
if (key === 'parent') {
continue
}
if (Array.isArray(child)) {
child.forEach(visit)
} else {
visit(child)
}
}
}
visit(selector.body)
return expressions
}
function unwrapShallowSelector(selector, shallowHooks) {
if (
selector?.type === 'CallExpression' &&
selector.callee.type === 'Identifier' &&
shallowHooks.has(selector.callee.name)
) {
return { selector: selector.arguments[0], shallow: true }
}
return { selector, shallow: false }
}
function isIdentitySelector(selector) {
if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
return false
}
const parameter = selector.params[0]
if (parameter?.type !== 'Identifier') {
return false
}
return returnedExpressions(selector).some(
(expression) => expression.type === 'Identifier' && expression.name === parameter.name
)
}
function isAllocatingExpression(expression) {
if (expression?.type === 'ConditionalExpression') {
return (
isAllocatingExpression(expression.consequent) || isAllocatingExpression(expression.alternate)
)
}
if (expression?.type === 'LogicalExpression') {
return isAllocatingExpression(expression.left) || isAllocatingExpression(expression.right)
}
if (
expression?.type === 'ArrayExpression' ||
expression?.type === 'ObjectExpression' ||
expression?.type === 'NewExpression'
) {
return true
}
if (expression?.type !== 'CallExpression') {
return false
}
const method = propertyName(expression.callee)
if (method && ALLOCATING_METHODS.has(method)) {
return true
}
const callee = expression.callee
return (
callee.type === 'MemberExpression' &&
identifierName(callee.object) === 'Object' &&
['assign', 'create', 'entries', 'fromEntries', 'keys', 'values'].includes(propertyName(callee))
)
}
function importedLocalName(specifier, importedName) {
if (specifier.type !== 'ImportSpecifier' || identifierName(specifier.imported) !== importedName) {
return null
}
return identifierName(specifier.local)
}
function createRuleState() {
return {
appStoreHooks: new Set(),
shallowHooks: new Set()
}
}
function recordImports(node, state) {
if (node.source?.value === 'zustand/react/shallow') {
for (const specifier of node.specifiers) {
const localName = importedLocalName(specifier, 'useShallow')
if (localName) {
state.shallowHooks.add(localName)
}
}
}
for (const specifier of node.specifiers) {
const localName = importedLocalName(specifier, 'useAppStore')
if (localName) {
state.appStoreHooks.add(localName)
}
}
}
function isAppStoreCall(node, state) {
return (
node.callee.type === 'Identifier' &&
state.appStoreHooks.has(node.callee.name) &&
node.optional !== true
)
}
function requireSelectorRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (isAppStoreCall(node, state) && node.arguments.length === 0) {
this.report({
node,
message:
'Pass a selector to useAppStore so the component does not rerender for every store write.'
})
}
}
}
}
function noIdentitySelectorRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (!isAppStoreCall(node, state)) {
return
}
const { selector } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
if (isIdentitySelector(selector)) {
this.report({
node: selector,
message:
'Select the smallest required fields instead of subscribing to the entire app store.'
})
}
}
}
}
function noFreshSelectorResultRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (!isAppStoreCall(node, state)) {
return
}
const { selector, shallow } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
if (shallow) {
return
}
const freshResult = returnedExpressions(selector).find(isAllocatingExpression)
if (freshResult) {
this.report({
node: freshResult,
message:
'This selector returns a fresh reference on every store write; select a stable field, cache the result, or use useShallow.'
})
}
}
}
}
function bindContext(createVisitors) {
return (context) => {
const visitors = createVisitors()
for (const [nodeType, visit] of Object.entries(visitors)) {
visitors[nodeType] = visit.bind(context)
}
return visitors
}
}
export default {
meta: { name: 'app-store-performance' },
rules: {
'require-selector': { create: bindContext(requireSelectorRule) },
'no-identity-selector': { create: bindContext(noIdentitySelectorRule) },
'no-fresh-selector-result': { create: bindContext(noFreshSelectorResultRule) }
}
}

View File

@ -0,0 +1,29 @@
const MESSAGE =
"qrcode is only reachable from mobile pairing. Use `import type` plus `await import('qrcode')` so startup does not parse its bundle."
function hasRuntimeImport(node) {
if (node.importKind === 'type') {
return false
}
return (
node.specifiers.length === 0 ||
node.specifiers.some((specifier) => specifier.importKind !== 'type')
)
}
export default {
meta: { name: 'mobile-pairing' },
rules: {
'no-eager-qrcode-import': {
create(context) {
return {
ImportDeclaration(node) {
if (node.source?.value === 'qrcode' && hasRuntimeImport(node)) {
context.report({ node, message: MESSAGE })
}
}
}
}
}
}
}

View File

@ -12,9 +12,18 @@
},
"jsPlugins": [{ "name": "react-doctor", "specifier": "oxlint-plugin-react-doctor" }],
"rules": {
"react-doctor/effect-needs-cleanup": "warn",
"react-doctor/no-array-index-as-key": "warn",
"react-doctor/no-adjust-state-on-prop-change": "warn",
"react-doctor/no-create-store-in-render": "warn",
"react-doctor/no-derived-state-effect": "warn",
"react-doctor/no-initialize-state": "warn"
"react-doctor/no-initialize-state": "warn",
"react-doctor/no-side-effect-in-state-updater-function": "warn",
"react-doctor/no-unstable-nested-components": "warn",
"react-doctor/zustand-no-fresh-selector-result": "warn",
"react-doctor/zustand-no-get-during-initialization": "warn",
"react-doctor/zustand-no-mutating-state": "warn",
"react-doctor/zustand-no-whole-store-destructure": "warn"
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/out"]
}

View File

@ -0,0 +1,76 @@
import { spawnSync } from 'node:child_process'
import { mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
const pluginPath = path.resolve('config/oxlint-plugins/app-store-performance.mjs')
const oxlintPath = path.resolve(
process.platform === 'win32' ? 'node_modules/.bin/oxlint.cmd' : 'node_modules/.bin/oxlint'
)
function lintSource(source) {
const directory = mkdtempSync(path.join(tmpdir(), 'orca-app-store-lint-'))
const sourcePath = path.join(directory, 'sample.tsx')
const configPath = path.join(directory, 'oxlint.json')
writeFileSync(sourcePath, source)
writeFileSync(
configPath,
JSON.stringify({
categories: {
correctness: 'off'
},
jsPlugins: [{ name: 'app-store-performance', specifier: pluginPath }],
rules: {
'app-store-performance/require-selector': 'warn',
'app-store-performance/no-identity-selector': 'warn',
'app-store-performance/no-fresh-selector-result': 'warn'
}
})
)
const result = spawnSync(oxlintPath, ['--config', configPath, '--format', 'json', sourcePath], {
encoding: 'utf8'
})
if (result.error) {
throw result.error
}
expect(result.status).toBe(0)
return JSON.parse(result.stdout).diagnostics
}
describe('app store performance Oxlint plugin', () => {
it('reports whole-store and fresh-reference subscriptions', () => {
const diagnostics = lintSource(`
import { useAppStore as useStore } from '@/store'
const WholeStore = () => useStore()
const Identity = () => useStore((state) => state)
const Fresh = () => useStore((state) => ({ active: state.active }))
const Conditional = () => useStore((state) => state.active ? state.items : [])
const Nested = () => useStore((state) => {
if (state.active) return state.items.filter(Boolean)
return state.items
})
`)
expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([
'app-store-performance(require-selector)',
'app-store-performance(no-identity-selector)',
'app-store-performance(no-fresh-selector-result)',
'app-store-performance(no-fresh-selector-result)',
'app-store-performance(no-fresh-selector-result)'
])
})
it('allows focused, cached, and useShallow selectors', () => {
const diagnostics = lintSource(`
import { useAppStore } from '@/store'
import { useShallow as shallow } from 'zustand/react/shallow'
const selectActive = (state) => state.active
const Focused = () => useAppStore(selectActive)
const Cached = () => useAppStore((state) => state.cachedProjection)
const Shallow = () => useAppStore(shallow((state) => ({ active: state.active })))
`)
expect(diagnostics).toEqual([])
})
})

View File

@ -0,0 +1,225 @@
import { execFileSync, spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { pathToFileURL } from 'node:url'
import { resolvePullRequestDiffBase } from './git-pull-request-diff-base.mjs'
const SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/
const OXLINT_SCANS = [
{
label: 'code quality',
args: [
'--config',
'config/oxlint-code-quality.json',
'--report-unused-disable-directives-severity',
'warn'
]
},
{
label: 'type-aware code quality',
args: ['--type-aware', '--config', 'config/oxlint-code-quality-type-aware.json']
},
{
label: 'React Doctor',
args: ['--config', 'config/oxlint-react-doctor.json']
}
]
export function parseAddedLineRanges(diff) {
const ranges = []
const hunkPattern = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/
for (const line of diff.split(/\r?\n/)) {
const match = hunkPattern.exec(line)
if (!match) {
continue
}
const start = Number.parseInt(match[1], 10)
const count = match[2] === undefined ? 1 : Number.parseInt(match[2], 10)
if (count > 0) {
ranges.push({ start, end: start + count - 1 })
}
}
return ranges
}
export function overlapsAddedLines(startLine, endLine, ranges) {
return ranges.some((range) => startLine <= range.end && endLine >= range.start)
}
function runGit(root, args, options = {}) {
return execFileSync('git', args, {
cwd: root,
encoding: options.encoding ?? 'utf8',
maxBuffer: 64 * 1024 * 1024
})
}
function splitNullDelimited(output) {
return output.split('\0').filter(Boolean)
}
function resolveBase(root, requestedBase) {
for (const candidate of [
requestedBase,
process.env.ORCA_CODE_QUALITY_BASE,
'origin/main',
'main'
]) {
if (!candidate) {
continue
}
const result = spawnSync('git', ['rev-parse', '--verify', `${candidate}^{commit}`], {
cwd: root,
stdio: 'ignore'
})
if (result.status === 0) {
return candidate
}
}
throw new Error('Pass the pull request base SHA or make origin/main available locally.')
}
export function collectAddedLineRanges(root, requestedBase) {
const base = resolveBase(root, requestedBase)
const mergeBase = runGit(root, ['merge-base', base, 'HEAD']).trim()
const comparisonBase = resolvePullRequestDiffBase(root, mergeBase)
const changedFiles = splitNullDelimited(
runGit(root, ['diff', '--name-only', '-z', '--diff-filter=ACMRTUB', comparisonBase, '--'])
)
const untrackedFiles = splitNullDelimited(
runGit(root, ['ls-files', '--others', '--exclude-standard', '-z'])
)
const rangesByFile = new Map()
for (const file of changedFiles) {
if (!SOURCE_FILE_PATTERN.test(file) || !existsSync(path.join(root, file))) {
continue
}
const diff = runGit(root, ['diff', '--unified=0', '--no-color', comparisonBase, '--', file])
const ranges = parseAddedLineRanges(diff)
if (ranges.length > 0) {
rangesByFile.set(file, ranges)
}
}
for (const file of untrackedFiles) {
const absolutePath = path.join(root, file)
if (!SOURCE_FILE_PATTERN.test(file) || !existsSync(absolutePath)) {
continue
}
const lineCount = readFileSync(absolutePath, 'utf8').split(/\r?\n/).length
rangesByFile.set(file, [{ start: 1, end: lineCount }])
}
return { base, comparisonBase, rangesByFile }
}
function parseOxlintOutput(stdout, label) {
const start = stdout.indexOf('{')
const end = stdout.lastIndexOf('}')
if (start === -1 || end === -1) {
throw new Error(`${label} did not return Oxlint JSON output.`)
}
return JSON.parse(stdout.slice(start, end + 1))
}
function normalizedDiagnosticPath(root, filename) {
const absolutePath = path.isAbsolute(filename) ? filename : path.join(root, filename)
return path.relative(root, absolutePath).split(path.sep).join('/')
}
function diagnosticLineRange(root, filename, span) {
const startLine = span.line
if (!Number.isInteger(startLine)) {
return null
}
if (!Number.isInteger(span.offset) || !Number.isInteger(span.length) || span.length === 0) {
return { start: startLine, end: startLine }
}
const absolutePath = path.isAbsolute(filename) ? filename : path.join(root, filename)
const source = readFileSync(absolutePath)
const highlighted = source.subarray(span.offset, span.offset + span.length).toString('utf8')
return { start: startLine, end: startLine + (highlighted.match(/\n/g)?.length ?? 0) }
}
export function diagnosticTouchesAddedLines(diagnostic, rangesByFile, root = process.cwd()) {
const file = normalizedDiagnosticPath(root, diagnostic.filename)
const ranges = rangesByFile.get(file)
if (!ranges) {
return false
}
return (diagnostic.labels ?? []).some((label) => {
const lineRange = diagnosticLineRange(root, diagnostic.filename, label.span)
return lineRange !== null && overlapsAddedLines(lineRange.start, lineRange.end, ranges)
})
}
function annotationValue(value) {
return String(value).replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A')
}
function printDiagnostic(diagnostic, root) {
const file = normalizedDiagnosticPath(root, diagnostic.filename)
const line = diagnostic.labels?.[0]?.span?.line ?? 1
const code = diagnostic.code ?? 'oxlint'
console.error(
`::error file=${annotationValue(file)},line=${line},title=${annotationValue(code)}::${annotationValue(diagnostic.message)}`
)
console.error(`${file}:${line} ${code}: ${diagnostic.message}`)
}
function runOxlintScan(root, scan, files) {
const pnpm = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
const result = spawnSync(pnpm, ['exec', 'oxlint', ...scan.args, '--format', 'json', ...files], {
cwd: root,
encoding: 'utf8',
maxBuffer: 128 * 1024 * 1024
})
if (result.error) {
throw result.error
}
if (!result.stdout.trim()) {
process.stderr.write(result.stderr)
throw new Error(`${scan.label} failed before producing diagnostics.`)
}
return parseOxlintOutput(result.stdout, scan.label).diagnostics ?? []
}
export function main(
root = process.cwd(),
requestedBase = process.argv.slice(2).find((argument) => argument !== '--')
) {
const { base, comparisonBase, rangesByFile } = collectAddedLineRanges(root, requestedBase)
const files = [...rangesByFile.keys()]
if (files.length === 0) {
console.log(`Changed-code quality gate: no changed JavaScript or TypeScript since ${base}.`)
return 0
}
let failures = 0
for (const scan of OXLINT_SCANS) {
const diagnostics = runOxlintScan(root, scan, files).filter((diagnostic) =>
diagnosticTouchesAddedLines(diagnostic, rangesByFile, root)
)
for (const diagnostic of diagnostics) {
printDiagnostic(diagnostic, root)
}
failures += diagnostics.length
console.log(
`${scan.label}: ${diagnostics.length} new finding(s) across ${files.length} changed file(s).`
)
}
if (failures > 0) {
console.error(
`Changed-code quality gate failed with ${failures} finding(s) since ${comparisonBase.slice(0, 12)}.`
)
return 1
}
console.log(`Changed-code quality gate passed since ${comparisonBase.slice(0, 12)}.`)
return 0
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exit(main())
}

View File

@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import {
diagnosticTouchesAddedLines,
overlapsAddedLines,
parseAddedLineRanges
} from './check-changed-code-quality.mjs'
describe('changed-code quality line matching', () => {
it('parses added and replaced hunk ranges while ignoring deletions', () => {
const ranges = parseAddedLineRanges(
['@@ -10,2 +10,3 @@', '@@ -20 +21 @@', '@@ -40,4 +42,0 @@', '@@ -50 +48,2 @@'].join('\n')
)
expect(ranges).toEqual([
{ start: 10, end: 12 },
{ start: 21, end: 21 },
{ start: 48, end: 49 }
])
})
it('matches diagnostics that overlap any added line', () => {
const ranges = [
{ start: 5, end: 7 },
{ start: 12, end: 12 }
]
expect(overlapsAddedLines(3, 5, ranges)).toBe(true)
expect(overlapsAddedLines(8, 11, ranges)).toBe(false)
expect(overlapsAddedLines(12, 14, ranges)).toBe(true)
})
it('normalizes absolute diagnostic paths before matching', () => {
const root = process.cwd()
const file = 'config/scripts/check-changed-code-quality.test.mjs'
const diagnostic = {
filename: `${root}/${file}`,
labels: [{ span: { line: 24 } }]
}
expect(
diagnosticTouchesAddedLines(diagnostic, new Map([[file, [{ start: 24, end: 24 }]]]), root)
).toBe(true)
})
})

View File

@ -0,0 +1,35 @@
import { spawnSync } from 'node:child_process'
import process from 'node:process'
import { resolvePullRequestDiffBase } from './git-pull-request-diff-base.mjs'
const requestedBase =
process.argv.slice(2).find((argument) => argument !== '--') ??
process.env.ORCA_CODE_QUALITY_BASE ??
'origin/main'
const base = resolvePullRequestDiffBase(process.cwd(), requestedBase)
const pnpm = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
const result = spawnSync(
pnpm,
[
'dlx',
'react-doctor@0.9.1',
'.',
'--yes',
'--scope',
'lines',
'--base',
base,
'--include-untracked',
'--no-dead-code',
'--no-supply-chain',
'--no-telemetry',
'--blocking',
'error'
],
{ stdio: 'inherit' }
)
if (result.error) {
throw result.error
}
process.exit(result.status ?? 1)

View File

@ -0,0 +1,23 @@
import { execFileSync } from 'node:child_process'
import process from 'node:process'
export function selectPullRequestDiffBase(requestedBase, headParents, eventName) {
if (eventName === 'pull_request' && headParents.length >= 2) {
return headParents[0]
}
return requestedBase
}
export function resolvePullRequestDiffBase(
root,
requestedBase,
eventName = process.env.GITHUB_EVENT_NAME
) {
const [, ...headParents] = execFileSync('git', ['rev-list', '--parents', '-n', '1', 'HEAD'], {
cwd: root,
encoding: 'utf8'
})
.trim()
.split(/\s+/)
return selectPullRequestDiffBase(requestedBase, headParents, eventName)
}

View File

@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest'
import { selectPullRequestDiffBase } from './git-pull-request-diff-base.mjs'
describe('pull request diff base selection', () => {
it('uses the merge commit first parent for pull request checkouts', () => {
expect(
selectPullRequestDiffBase('event-base', ['current-base', 'pull-request-head'], 'pull_request')
).toBe('current-base')
})
it('keeps the requested base outside synthetic pull request merges', () => {
expect(selectPullRequestDiffBase('requested-base', ['parent'], 'pull_request')).toBe(
'requested-base'
)
expect(selectPullRequestDiffBase('requested-base', ['parent', 'other'], 'push')).toBe(
'requested-base'
)
})
})

View File

@ -0,0 +1,47 @@
import { spawnSync } from 'node:child_process'
import { mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
const pluginPath = path.resolve('config/oxlint-plugins/mobile-pairing-qrcode-import.mjs')
const oxlintPath = path.resolve(
process.platform === 'win32' ? 'node_modules/.bin/oxlint.cmd' : 'node_modules/.bin/oxlint'
)
function lintSource(source) {
const directory = mkdtempSync(path.join(tmpdir(), 'orca-qrcode-import-lint-'))
const sourcePath = path.join(directory, 'sample.ts')
const configPath = path.join(directory, 'oxlint.json')
writeFileSync(sourcePath, source)
writeFileSync(
configPath,
JSON.stringify({
categories: { correctness: 'off' },
jsPlugins: [{ name: 'mobile-pairing', specifier: pluginPath }],
rules: { 'mobile-pairing/no-eager-qrcode-import': 'error' }
})
)
const result = spawnSync(oxlintPath, ['--config', configPath, '--format', 'json', sourcePath], {
encoding: 'utf8'
})
if (result.error) {
throw result.error
}
return JSON.parse(result.stdout).diagnostics
}
describe('mobile pairing qrcode import rule', () => {
it('rejects eager runtime imports', () => {
const diagnostics = lintSource("import QRCode from 'qrcode'\nvoid QRCode")
expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([
'mobile-pairing(no-eager-qrcode-import)'
])
})
it('allows type-only and lazy imports', () => {
expect(lintSource("import type QRCode from 'qrcode'\nlet qr: typeof QRCode")).toEqual([])
expect(lintSource("const QRCode = await import('qrcode')\nvoid QRCode")).toEqual([])
})
})

View File

@ -0,0 +1,81 @@
#!/usr/bin/env node
import { performance } from 'node:perf_hooks'
import process from 'node:process'
import { createStore } from 'zustand/vanilla'
const SUBSCRIBERS = Number.parseInt(process.env.ORCA_ZUSTAND_BENCH_SUBSCRIBERS ?? '2500', 10)
const WRITES = Number.parseInt(process.env.ORCA_ZUSTAND_BENCH_WRITES ?? '2000', 10)
const MAX_MILLISECONDS_PER_WRITE = Number.parseFloat(
process.env.ORCA_ZUSTAND_BENCH_MAX_MS_PER_WRITE ?? '5'
)
for (const [name, value] of [
['ORCA_ZUSTAND_BENCH_SUBSCRIBERS', SUBSCRIBERS],
['ORCA_ZUSTAND_BENCH_WRITES', WRITES],
['ORCA_ZUSTAND_BENCH_MAX_MS_PER_WRITE', MAX_MILLISECONDS_PER_WRITE]
]) {
if (!Number.isFinite(value) || value <= 0) {
throw new Error(`${name} must be positive, received ${value}`)
}
}
function measureRound() {
const stableProjection = Object.freeze({ activeRepoId: 'repo-1' })
const store = createStore(() => ({ unrelatedWrite: 0, stableProjection }))
let selectorRuns = 0
let renderInvalidations = 0
const unsubscribe = Array.from({ length: SUBSCRIBERS }, () => {
let previous = store.getState().stableProjection
return store.subscribe((state) => {
selectorRuns += 1
const next = state.stableProjection
if (!Object.is(previous, next)) {
renderInvalidations += 1
}
previous = next
})
})
const start = performance.now()
for (let index = 1; index <= WRITES; index += 1) {
store.setState({ unrelatedWrite: index })
}
const elapsed = performance.now() - start
for (const release of unsubscribe) {
release()
}
return { elapsed, selectorRuns, renderInvalidations }
}
measureRound()
const rounds = Array.from({ length: 5 }, measureRound).sort(
(left, right) => left.elapsed - right.elapsed
)
const median = rounds[2]
const expectedSelectorRuns = SUBSCRIBERS * WRITES
const millisecondsPerWrite = median.elapsed / WRITES
if (median.selectorRuns !== expectedSelectorRuns) {
throw new Error(
`Expected ${expectedSelectorRuns} selector runs, observed ${median.selectorRuns}; update the fan-out model.`
)
}
if (median.renderInvalidations !== 0) {
throw new Error(
`${median.renderInvalidations} unrelated writes changed a stable selector result.`
)
}
console.log(
`Zustand fan-out: ${SUBSCRIBERS} subscribers × ${WRITES} unrelated writes = ${expectedSelectorRuns.toLocaleString()} selector runs`
)
console.log(
`Median ${median.elapsed.toFixed(2)} ms total, ${millisecondsPerWrite.toFixed(4)} ms/write, 0 render invalidations`
)
if (process.argv.includes('--check') && millisecondsPerWrite > MAX_MILLISECONDS_PER_WRITE) {
console.error(
`Zustand fan-out exceeded ${MAX_MILLISECONDS_PER_WRITE.toFixed(2)} ms/write. Inspect selector work and store subscription growth.`
)
process.exit(1)
}

View File

@ -12,6 +12,14 @@
"scripts": {
"format": "oxfmt --write .",
"lint": "oxlint && pnpm run lint:switch-exhaustiveness && node config/scripts/check-styled-scrollbars.mjs && pnpm run check:quadratic-buffer-concat && pnpm run check:reliability-gates && pnpm run check:max-lines-ratchet && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-coverage",
"audit:code-quality": "pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run audit:react-doctor",
"audit:code-quality:native": "oxlint --config config/oxlint-code-quality.json --report-unused-disable-directives-severity warn src config tests mobile",
"audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests mobile",
"audit:react-doctor": "pnpm dlx react-doctor@0.9.1 . --yes --no-supply-chain --no-telemetry --blocking none",
"check:code-quality:changed": "node config/scripts/check-changed-code-quality.mjs",
"check:react-doctor:changed": "node config/scripts/check-react-doctor-changed.mjs",
"check:zustand-selector-fanout": "node config/scripts/zustand-selector-fanout-benchmark.mjs --check",
"doctor": "pnpm dlx react-doctor@0.9.1 . --no-telemetry",
"lint:react-doctor": "oxlint --config config/oxlint-react-doctor.json",
"lint:react-doctor:changed": "node config/scripts/lint-react-doctor-changed.mjs",
"lint:switch-exhaustiveness": "oxlint --type-aware --config config/oxlint-switch-exhaustiveness.json src/main src/preload src/shared src/relay src/cli src/renderer/src config tests --quiet",
@ -104,6 +112,7 @@
"bench:startup": "pnpm run ensure:electron-runtime && node tools/benchmarks/startup-time-bench.mjs",
"bench:daemon-coldstart": "pnpm run ensure:electron-runtime && node tools/benchmarks/daemon-coldstart-bench.mjs",
"bench:main-thread-jank": "pnpm run ensure:electron-runtime && node tools/benchmarks/main-thread-jank-bench.mjs",
"bench:zustand-selector-fanout": "node config/scripts/zustand-selector-fanout-benchmark.mjs",
"bench:multi-workspace-typing": "pnpm run ensure:electron-runtime && node config/scripts/run-multi-workspace-typing-bench.mjs",
"bench:cold-park-reveal": "pnpm run ensure:electron-runtime && node tools/benchmarks/terminal-cold-park-reveal-bench.mjs",
"bench:cold-park-resource": "pnpm run ensure:electron-runtime && node tools/benchmarks/terminal-cold-park-resource-bench.mjs",
@ -197,9 +206,9 @@
"mermaid": "^11.15.0",
"monaco-editor": "^0.55.1",
"oxfmt": "^0.52.0",
"oxlint": "^1.71.0",
"oxlint-plugin-react-doctor": "0.2.10",
"oxlint-tsgolint": "0.23.0",
"oxlint": "^1.75.0",
"oxlint-plugin-react-doctor": "0.9.1",
"oxlint-tsgolint": "7.0.2001",
"pdfjs-dist": "^5.7.284",
"pngjs": "^7.0.0",
"radix-ui": "^1.6.2",
@ -230,7 +239,7 @@
"vitest": "^4.1.5",
"vscode-oniguruma": "^2.0.1",
"vscode-textmate": "^9.3.2",
"zustand": "^5.0.13"
"zustand": "^5.0.14"
},
"optionalDependencies": {
"sherpa-onnx-darwin-arm64": "1.12.37",

View File

@ -286,14 +286,14 @@ importers:
specifier: ^0.52.0
version: 0.52.0
oxlint:
specifier: ^1.71.0
version: 1.71.0(oxlint-tsgolint@0.23.0)
specifier: ^1.75.0
version: 1.75.0(oxlint-tsgolint@7.0.2001)
oxlint-plugin-react-doctor:
specifier: 0.2.10
version: 0.2.10
specifier: 0.9.1
version: 0.9.1
oxlint-tsgolint:
specifier: 0.23.0
version: 0.23.0
specifier: 7.0.2001
version: 7.0.2001
pdfjs-dist:
specifier: ^5.7.284
version: 5.7.284
@ -377,7 +377,7 @@ importers:
version: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)
vitest:
specifier: ^4.1.5
version: 4.1.5(@types/node@25.9.5)(happy-dom@20.9.0)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.9.0)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
vscode-oniguruma:
specifier: ^2.0.1
version: 2.0.1
@ -385,8 +385,8 @@ importers:
specifier: ^9.3.2
version: 9.3.2
zustand:
specifier: ^5.0.13
version: 5.0.13(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))
specifier: ^5.0.14
version: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))
optionalDependencies:
sherpa-onnx-darwin-arm64:
specifier: 1.12.37
@ -1079,6 +1079,137 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@opentelemetry/api@1.9.1':
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
engines: {node: '>=8.0.0'}
'@oxc-parser/binding-android-arm-eabi@0.141.0':
resolution: {integrity: sha512-jk7086MFvR/T4DG9IY7MKBVt1PMxvSZoz/TvnifodvS0pjghVwJHRttnAExhlwdMOgHv1TmLdENnbNpYk2zjvA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxc-parser/binding-android-arm64@0.141.0':
resolution: {integrity: sha512-a4XDQ27ZT7e7zwAlxJDTiCA7IBGWDuy2+MhFq85Of7XlBSmpkfcBFml11q0Zx6f7RMuI0B4xCtt2ytBS4yOptg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxc-parser/binding-darwin-arm64@0.141.0':
resolution: {integrity: sha512-m/kVk6rzYmBeHYnz+1Y5fod00AVTTxMbC71azFfm/zjx1j9XxwKtA0+VfkKuVMC8rbghb9TtfevnuWZa9OuPEg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxc-parser/binding-darwin-x64@0.141.0':
resolution: {integrity: sha512-o0X+6KZlfucWU/v5oKRQPwdFXsXAjW8jmpo/Gpw/qyKsbKtlfkHoeH9Bjp/m13TwjewvJnCkwF0DWzgpC4HjTQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxc-parser/binding-freebsd-x64@0.141.0':
resolution: {integrity: sha512-W5KbTnNkTMMMylqj6dYqnsXvkmESVPodPKYLJ5zdzIPdl9fUJtolkpUeSzYEbGGYB4a4A4avl3EePnZ/wLIdJg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxc-parser/binding-linux-arm-gnueabihf@0.141.0':
resolution: {integrity: sha512-g3dtbJa8zeOGK36Sr9cQavsdi5H/ie2hVjrSjIxsNAR1qZA40ZYVXnfdfoMAlq8CmB9qFL1yhsSCUHeNmdmt8w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxc-parser/binding-linux-arm-musleabihf@0.141.0':
resolution: {integrity: sha512-e6hwQqd+3lvP13G2jxvFpoA7dzHcFLN+Mq47JCVMtdNHbbyBRo756JCtbbJH6ca8inTfyqZoqBmS3vhQlzAK2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxc-parser/binding-linux-arm64-gnu@0.141.0':
resolution: {integrity: sha512-vXz2BLAuypA+4MLyBg94pzEo6THVnzYnCtAjXoihIIQo0t2pnp/AmW+SH1EI+4VbuJnC//KplIJ5yyaCGua4jA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-arm64-musl@0.141.0':
resolution: {integrity: sha512-jMkS/EztNW34HKsXIaT/SoHcmtocq/vWhwFOVduF9kduuuRIVwfwQ6uxzIO+qPKSXdd2TXt54of0BJ2zFMXnmw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxc-parser/binding-linux-ppc64-gnu@0.141.0':
resolution: {integrity: sha512-vo+MR+n3zQJ6Mq92hiP084NZcgDv5iJlVR02gMf28neMvVT1tKVm7VeiW/DxhdqOi3QLeaXIk9cUcLL1qrkngw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-riscv64-gnu@0.141.0':
resolution: {integrity: sha512-oh80w+7RuiO5gBp9Jnoa/H8Qlt3JsHL2MkW+0dwEdlDMdslVZX/YsekSK6EeyEenY66/mhCfypsNATQ7Ph3qlQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-riscv64-musl@0.141.0':
resolution: {integrity: sha512-LOyEmFA8sCnYbEXP1+iQvCC/P1YXHMA/t6x1Ksp0Y9VwhLFsiBJFzV1zIxrOIE2LKaGGhDjQ29xq9cbq6omDXA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxc-parser/binding-linux-s390x-gnu@0.141.0':
resolution: {integrity: sha512-3wnwk/l1CvszVE5TJR1wSl/zSEfydRqrNhn6s7Vr9IzSJpUQIroqVsIoPARHRFA+FQwkxAFDAHDAasa7v8OobQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-x64-gnu@0.141.0':
resolution: {integrity: sha512-qtyQVAAebFq57B2tifTlel3TgGqUtsYNI/e+p6aya9rN9lOZVTDvr215fGYSA9XWooxzMxDiVxkBLk2jQHbsOQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-x64-musl@0.141.0':
resolution: {integrity: sha512-SkGV1nKw40roEc94pv5EaaeH2ay14G6+roe8Q0wIUC1LcEKxzKW921h7+ZuZX0D3q2Mb/7aSFmxEVqnko3lPRw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxc-parser/binding-openharmony-arm64@0.141.0':
resolution: {integrity: sha512-cVgDM7n8QziQqOaP5hNgUYfMG7S/ZeuPxFWXnnHRv7rh025COk0rfQ6eEdKG3j/GaUuyvNZN4ifF1J8KmuXLLA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxc-parser/binding-wasm32-wasi@0.141.0':
resolution: {integrity: sha512-HggH++Fkn3OilBn+bs3jpgIFQa34oMAyUUHy0vpGum+gt1Eb5nyLc8dNU/RAPSw6lsLrx7ncKtHSZE+3Sp0l2g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@oxc-parser/binding-win32-arm64-msvc@0.141.0':
resolution: {integrity: sha512-KLSEH9GwgbrqbJOjtGHt9STw96s+78yDzp7IDN8Lno+7Ut9sNBfZ4jYZIz4mD50qmWUjoOI7i9I6UENbhNbMZQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxc-parser/binding-win32-ia32-msvc@0.141.0':
resolution: {integrity: sha512-9UVWUOOCI/1YkiSSNjg2zyBJYM9E/t1A/8GNobd48JDn/fQ6mzxcVO3H08jb3rAaW/B1VBf8eCORTvSsO9T08g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxc-parser/binding-win32-x64-msvc@0.141.0':
resolution: {integrity: sha512-HI/wsvbWT5RHHw5c37D0fEgeTd8/1Q4OJs5jUmEBc17VZFG6SsCIe4barq7NsAPPks/JW+3ayi3Rp+PQI5h4Kg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@oxc-project/runtime@0.101.0':
resolution: {integrity: sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -1086,6 +1217,9 @@ packages:
'@oxc-project/types@0.101.0':
resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==}
'@oxc-project/types@0.141.0':
resolution: {integrity: sha512-S4as7z0j0xQkXcJlyY5ehntwK8/wRkQb9Cyqw+J/N2rkWGQGK0SxD6X6DhQTc7qsxVTBxXbxZtBJh3mr3PtIzQ==}
'@oxfmt/binding-android-arm-eabi@0.52.0':
resolution: {integrity: sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -1208,154 +1342,154 @@ packages:
cpu: [x64]
os: [win32]
'@oxlint-tsgolint/darwin-arm64@0.23.0':
resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==}
'@oxlint-tsgolint/darwin-arm64@7.0.2001':
resolution: {integrity: sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==}
cpu: [arm64]
os: [darwin]
'@oxlint-tsgolint/darwin-x64@0.23.0':
resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==}
'@oxlint-tsgolint/darwin-x64@7.0.2001':
resolution: {integrity: sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==}
cpu: [x64]
os: [darwin]
'@oxlint-tsgolint/linux-arm64@0.23.0':
resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==}
'@oxlint-tsgolint/linux-arm64@7.0.2001':
resolution: {integrity: sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==}
cpu: [arm64]
os: [linux]
'@oxlint-tsgolint/linux-x64@0.23.0':
resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==}
'@oxlint-tsgolint/linux-x64@7.0.2001':
resolution: {integrity: sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==}
cpu: [x64]
os: [linux]
'@oxlint-tsgolint/win32-arm64@0.23.0':
resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==}
'@oxlint-tsgolint/win32-arm64@7.0.2001':
resolution: {integrity: sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==}
cpu: [arm64]
os: [win32]
'@oxlint-tsgolint/win32-x64@0.23.0':
resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==}
'@oxlint-tsgolint/win32-x64@7.0.2001':
resolution: {integrity: sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==}
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.71.0':
resolution: {integrity: sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==}
'@oxlint/binding-android-arm-eabi@1.75.0':
resolution: {integrity: sha512-lutovtFzJqlRaqpZrCqSSGaHZzl9nIxxpjLzhSRLunN6dCLylj0uzlCyQGaQDIys7rrv8kVXiFO+R4Zpn0bX7g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@oxlint/binding-android-arm64@1.71.0':
resolution: {integrity: sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==}
'@oxlint/binding-android-arm64@1.75.0':
resolution: {integrity: sha512-hXI0hDgHkw4w5nfru72aG7y+2iQJmC4waH/KV6H/hbgA6yAP5jYNx0P9yug15Hs0tWl/+mda3Jjn/2gmDT48tw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@oxlint/binding-darwin-arm64@1.71.0':
resolution: {integrity: sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==}
'@oxlint/binding-darwin-arm64@1.75.0':
resolution: {integrity: sha512-D91BWbK/dMYfCcrghspPIuKs2D9LF4Z/OabVSQjw1AO6PWxArD7teDA48bm0ySFqWDaPVqmQRl5GMWNglTXyrQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@oxlint/binding-darwin-x64@1.71.0':
resolution: {integrity: sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==}
'@oxlint/binding-darwin-x64@1.75.0':
resolution: {integrity: sha512-02mpwzf12BonZ6PT0TuQoomvEh2kVl2WGBIKWezCyToIS+rYkQZ6GXnARBAl9A4Ovm2V+Xe7M4KretyqmmcnJQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@oxlint/binding-freebsd-x64@1.71.0':
resolution: {integrity: sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==}
'@oxlint/binding-freebsd-x64@1.75.0':
resolution: {integrity: sha512-qZJgLnDaBsiL5YESx2t/TZ8eXkL9fEkKoXEdzegROhlz9A0lgyGnZ0dAzJrh7LJAHQl2K9RdRueN2s/9N7+odg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@oxlint/binding-linux-arm-gnueabihf@1.71.0':
resolution: {integrity: sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==}
'@oxlint/binding-linux-arm-gnueabihf@1.75.0':
resolution: {integrity: sha512-7XlaWA5BJD3XpCfrEqjEe6Zseeb14S7QGa304XfwKignRaKQ+eIj775BQ7nIslggWickl4IsPUFqJ+/gAyNHVg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm-musleabihf@1.71.0':
resolution: {integrity: sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==}
'@oxlint/binding-linux-arm-musleabihf@1.75.0':
resolution: {integrity: sha512-av6Tpv8yrcMMMOadOqENBhlsLRcGFXXwoQ0hzHhsmS9FJ4Wioy8we427GbcMe2XTxmL2e60T67H1Dyr3up+tAA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@oxlint/binding-linux-arm64-gnu@1.71.0':
resolution: {integrity: sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==}
'@oxlint/binding-linux-arm64-gnu@1.75.0':
resolution: {integrity: sha512-WcUhd8fHT5plrA14lANevl+hOl815mVI5t2hU21oFWrZKFXIVV/Sr4rWQV0NzSvzBupbMLNc5ErEA6Ehxh5jMg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-arm64-musl@1.71.0':
resolution: {integrity: sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==}
'@oxlint/binding-linux-arm64-musl@1.75.0':
resolution: {integrity: sha512-UWzp5wRHFe/ESO3+eEaxXsTkYTGLYjnTsi/I5neEacXSItQ6WNleapfOAeA4x2b8nyhJ4uQxqvtv9pHv8kWJtQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-ppc64-gnu@1.71.0':
resolution: {integrity: sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==}
'@oxlint/binding-linux-ppc64-gnu@1.75.0':
resolution: {integrity: sha512-XEVRwGMLKCUKrvhLAz4F6AIh8MJrQVdSZtAmPpRZt9tGPsUnamPOcl3dS/ZQzJnar/Ymgc//+xho0L60Emzuxg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-gnu@1.71.0':
resolution: {integrity: sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==}
'@oxlint/binding-linux-riscv64-gnu@1.75.0':
resolution: {integrity: sha512-mAG4DUXqfLC8cTjMD2kt3jDmVzFREYtDyeLNdLdsCcBc4Zbl2EMuiFektGBilQwkNjYnMvCqJs55U+Hyb+b+jw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-riscv64-musl@1.71.0':
resolution: {integrity: sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==}
'@oxlint/binding-linux-riscv64-musl@1.75.0':
resolution: {integrity: sha512-95hrAvriAlI+pekSomTFIn0+bawMDlDwTNVmdjsFusTHyL2JWh7TWvRNG/Lkim72uN8OiCcO9wcaC6omLP5E3w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@oxlint/binding-linux-s390x-gnu@1.71.0':
resolution: {integrity: sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==}
'@oxlint/binding-linux-s390x-gnu@1.75.0':
resolution: {integrity: sha512-4b6f2+FrtruAESrCqIKcrarzfrSx+wk2QNcp+RT91/Prc+pMQMAfyZ1rG1c3tFQNl8Bc616tx40uNXyxNBRPbQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-gnu@1.71.0':
resolution: {integrity: sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==}
'@oxlint/binding-linux-x64-gnu@1.75.0':
resolution: {integrity: sha512-nshAhrUvXFUWOvqQ2soIw7HFNWvpvEV4o0cYSqPtzLiPF5gKyYTDOOTJ6Rn8g8K/iGvPIrbDA4v8+5MvnjJrrg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@oxlint/binding-linux-x64-musl@1.71.0':
resolution: {integrity: sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==}
'@oxlint/binding-linux-x64-musl@1.75.0':
resolution: {integrity: sha512-e4jNxLKnxLC6sYBQRxrI2pgIIxnmMtF8U/VwNYcjTT/CLS+spH624cYVnj07bTKwaEWT37/e025isOs6j/0xqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@oxlint/binding-openharmony-arm64@1.71.0':
resolution: {integrity: sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==}
'@oxlint/binding-openharmony-arm64@1.75.0':
resolution: {integrity: sha512-hZ2lH+1qLf/DiEP9UWuQTK2JWj/BgvMB4jhIV4SmNU1wfEiYYX4TynQyAZXx0j9X4qRYizAL042SKaV+8ynh4w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@oxlint/binding-win32-arm64-msvc@1.71.0':
resolution: {integrity: sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==}
'@oxlint/binding-win32-arm64-msvc@1.75.0':
resolution: {integrity: sha512-Ilj6PNzGDS3bCU0MSJH7Msh0NhH+T/mRp2shwg+q+GHeVlPwP5LEboW96aW+3kVKFk6zYZy1Xi5pZkqZh6X8KQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@oxlint/binding-win32-ia32-msvc@1.71.0':
resolution: {integrity: sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==}
'@oxlint/binding-win32-ia32-msvc@1.75.0':
resolution: {integrity: sha512-QVit2nOEOiPhkmsrksPSkoGCdnZRNkspt8fwoYyP09te1VEbnSj4LAxua4rc8FKTmWkySVe05j8iz9GXYfF1AQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
'@oxlint/binding-win32-x64-msvc@1.71.0':
resolution: {integrity: sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==}
'@oxlint/binding-win32-x64-msvc@1.75.0':
resolution: {integrity: sha512-DSxnNkBUAYARPwJtR12Ig3deWr8w0H997xP6jy33i+e0SyYJw8FKuz4+cZtpmPEhQmvlPJE3X/2vNxDmLkd/rA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@ -5135,6 +5269,10 @@ packages:
outvariant@1.4.3:
resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
oxc-parser@0.141.0:
resolution: {integrity: sha512-uFkGGr1KMWd6aWv9UAqooYrN78trw8MWWmoPvgWokfBEUq1+eiIQ+qfj3wokhy0fxtZWZk+0dHoS7/yRTJtd6w==}
engines: {node: ^20.19.0 || >=22.12.0}
oxfmt@0.52.0:
resolution: {integrity: sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -5148,20 +5286,20 @@ packages:
vite-plus:
optional: true
oxlint-plugin-react-doctor@0.2.10:
resolution: {integrity: sha512-n36QdOLz4k9EEWod+vhki9/h29x/PL4nS91nWSk6AIr7HAL+rc6fkwUcVLgg3NhUVlaL/VOfYiIm4cVxyIIdGg==}
engines: {node: ^20.19.0 || >=22.12.0}
oxlint-plugin-react-doctor@0.9.1:
resolution: {integrity: sha512-yCW8USbiuszbVsUMN4fL1iU7mRu3Ae3w96+k/xqCyWvW6bF6DzCMtLy5N/w6WLRX78iQsWxVqW/SEgzRBXLfsA==}
engines: {node: ^20.19.0 || >=22.13.0}
oxlint-tsgolint@0.23.0:
resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==}
oxlint-tsgolint@7.0.2001:
resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==}
hasBin: true
oxlint@1.71.0:
resolution: {integrity: sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==}
oxlint@1.75.0:
resolution: {integrity: sha512-m9WzjRcRYA/uqIZDa9tclrieoPJ/ln1QYTKdFx6NUOs8uY5DiHlIwRQoCrHT6OM6O3ww3l2skY5gO7G7ZphE7g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
oxlint-tsgolint: '>=0.22.1'
oxlint-tsgolint: '>=7.0.2001'
vite-plus: '*'
peerDependenciesMeta:
oxlint-tsgolint:
@ -6432,8 +6570,8 @@ packages:
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
zustand@5.0.13:
resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==}
zustand@5.0.14:
resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
@ -7159,10 +7297,79 @@ snapshots:
'@open-draft/until@2.1.0':
optional: true
'@opentelemetry/api@1.9.1':
optional: true
'@oxc-parser/binding-android-arm-eabi@0.141.0':
optional: true
'@oxc-parser/binding-android-arm64@0.141.0':
optional: true
'@oxc-parser/binding-darwin-arm64@0.141.0':
optional: true
'@oxc-parser/binding-darwin-x64@0.141.0':
optional: true
'@oxc-parser/binding-freebsd-x64@0.141.0':
optional: true
'@oxc-parser/binding-linux-arm-gnueabihf@0.141.0':
optional: true
'@oxc-parser/binding-linux-arm-musleabihf@0.141.0':
optional: true
'@oxc-parser/binding-linux-arm64-gnu@0.141.0':
optional: true
'@oxc-parser/binding-linux-arm64-musl@0.141.0':
optional: true
'@oxc-parser/binding-linux-ppc64-gnu@0.141.0':
optional: true
'@oxc-parser/binding-linux-riscv64-gnu@0.141.0':
optional: true
'@oxc-parser/binding-linux-riscv64-musl@0.141.0':
optional: true
'@oxc-parser/binding-linux-s390x-gnu@0.141.0':
optional: true
'@oxc-parser/binding-linux-x64-gnu@0.141.0':
optional: true
'@oxc-parser/binding-linux-x64-musl@0.141.0':
optional: true
'@oxc-parser/binding-openharmony-arm64@0.141.0':
optional: true
'@oxc-parser/binding-wasm32-wasi@0.141.0':
dependencies:
'@emnapi/core': 1.11.2
'@emnapi/runtime': 1.11.2
'@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)
optional: true
'@oxc-parser/binding-win32-arm64-msvc@0.141.0':
optional: true
'@oxc-parser/binding-win32-ia32-msvc@0.141.0':
optional: true
'@oxc-parser/binding-win32-x64-msvc@0.141.0':
optional: true
'@oxc-project/runtime@0.101.0': {}
'@oxc-project/types@0.101.0': {}
'@oxc-project/types@0.141.0': {}
'@oxfmt/binding-android-arm-eabi@0.52.0':
optional: true
@ -7220,79 +7427,79 @@ snapshots:
'@oxfmt/binding-win32-x64-msvc@0.52.0':
optional: true
'@oxlint-tsgolint/darwin-arm64@0.23.0':
'@oxlint-tsgolint/darwin-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/darwin-x64@0.23.0':
'@oxlint-tsgolint/darwin-x64@7.0.2001':
optional: true
'@oxlint-tsgolint/linux-arm64@0.23.0':
'@oxlint-tsgolint/linux-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/linux-x64@0.23.0':
'@oxlint-tsgolint/linux-x64@7.0.2001':
optional: true
'@oxlint-tsgolint/win32-arm64@0.23.0':
'@oxlint-tsgolint/win32-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/win32-x64@0.23.0':
'@oxlint-tsgolint/win32-x64@7.0.2001':
optional: true
'@oxlint/binding-android-arm-eabi@1.71.0':
'@oxlint/binding-android-arm-eabi@1.75.0':
optional: true
'@oxlint/binding-android-arm64@1.71.0':
'@oxlint/binding-android-arm64@1.75.0':
optional: true
'@oxlint/binding-darwin-arm64@1.71.0':
'@oxlint/binding-darwin-arm64@1.75.0':
optional: true
'@oxlint/binding-darwin-x64@1.71.0':
'@oxlint/binding-darwin-x64@1.75.0':
optional: true
'@oxlint/binding-freebsd-x64@1.71.0':
'@oxlint/binding-freebsd-x64@1.75.0':
optional: true
'@oxlint/binding-linux-arm-gnueabihf@1.71.0':
'@oxlint/binding-linux-arm-gnueabihf@1.75.0':
optional: true
'@oxlint/binding-linux-arm-musleabihf@1.71.0':
'@oxlint/binding-linux-arm-musleabihf@1.75.0':
optional: true
'@oxlint/binding-linux-arm64-gnu@1.71.0':
'@oxlint/binding-linux-arm64-gnu@1.75.0':
optional: true
'@oxlint/binding-linux-arm64-musl@1.71.0':
'@oxlint/binding-linux-arm64-musl@1.75.0':
optional: true
'@oxlint/binding-linux-ppc64-gnu@1.71.0':
'@oxlint/binding-linux-ppc64-gnu@1.75.0':
optional: true
'@oxlint/binding-linux-riscv64-gnu@1.71.0':
'@oxlint/binding-linux-riscv64-gnu@1.75.0':
optional: true
'@oxlint/binding-linux-riscv64-musl@1.71.0':
'@oxlint/binding-linux-riscv64-musl@1.75.0':
optional: true
'@oxlint/binding-linux-s390x-gnu@1.71.0':
'@oxlint/binding-linux-s390x-gnu@1.75.0':
optional: true
'@oxlint/binding-linux-x64-gnu@1.71.0':
'@oxlint/binding-linux-x64-gnu@1.75.0':
optional: true
'@oxlint/binding-linux-x64-musl@1.71.0':
'@oxlint/binding-linux-x64-musl@1.75.0':
optional: true
'@oxlint/binding-openharmony-arm64@1.71.0':
'@oxlint/binding-openharmony-arm64@1.75.0':
optional: true
'@oxlint/binding-win32-arm64-msvc@1.71.0':
'@oxlint/binding-win32-arm64-msvc@1.75.0':
optional: true
'@oxlint/binding-win32-ia32-msvc@1.71.0':
'@oxlint/binding-win32-ia32-msvc@1.75.0':
optional: true
'@oxlint/binding-win32-x64-msvc@1.71.0':
'@oxlint/binding-win32-x64-msvc@1.75.0':
optional: true
'@parcel/watcher-android-arm64@2.5.6':
@ -11417,6 +11624,31 @@ snapshots:
outvariant@1.4.3:
optional: true
oxc-parser@0.141.0:
dependencies:
'@oxc-project/types': 0.141.0
optionalDependencies:
'@oxc-parser/binding-android-arm-eabi': 0.141.0
'@oxc-parser/binding-android-arm64': 0.141.0
'@oxc-parser/binding-darwin-arm64': 0.141.0
'@oxc-parser/binding-darwin-x64': 0.141.0
'@oxc-parser/binding-freebsd-x64': 0.141.0
'@oxc-parser/binding-linux-arm-gnueabihf': 0.141.0
'@oxc-parser/binding-linux-arm-musleabihf': 0.141.0
'@oxc-parser/binding-linux-arm64-gnu': 0.141.0
'@oxc-parser/binding-linux-arm64-musl': 0.141.0
'@oxc-parser/binding-linux-ppc64-gnu': 0.141.0
'@oxc-parser/binding-linux-riscv64-gnu': 0.141.0
'@oxc-parser/binding-linux-riscv64-musl': 0.141.0
'@oxc-parser/binding-linux-s390x-gnu': 0.141.0
'@oxc-parser/binding-linux-x64-gnu': 0.141.0
'@oxc-parser/binding-linux-x64-musl': 0.141.0
'@oxc-parser/binding-openharmony-arm64': 0.141.0
'@oxc-parser/binding-wasm32-wasi': 0.141.0
'@oxc-parser/binding-win32-arm64-msvc': 0.141.0
'@oxc-parser/binding-win32-ia32-msvc': 0.141.0
'@oxc-parser/binding-win32-x64-msvc': 0.141.0
oxfmt@0.52.0:
dependencies:
tinypool: 2.1.0
@ -11441,43 +11673,44 @@ snapshots:
'@oxfmt/binding-win32-ia32-msvc': 0.52.0
'@oxfmt/binding-win32-x64-msvc': 0.52.0
oxlint-plugin-react-doctor@0.2.10:
oxlint-plugin-react-doctor@0.9.1:
dependencies:
'@typescript-eslint/types': 8.60.0
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
oxc-parser: 0.141.0
oxlint-tsgolint@0.23.0:
oxlint-tsgolint@7.0.2001:
optionalDependencies:
'@oxlint-tsgolint/darwin-arm64': 0.23.0
'@oxlint-tsgolint/darwin-x64': 0.23.0
'@oxlint-tsgolint/linux-arm64': 0.23.0
'@oxlint-tsgolint/linux-x64': 0.23.0
'@oxlint-tsgolint/win32-arm64': 0.23.0
'@oxlint-tsgolint/win32-x64': 0.23.0
'@oxlint-tsgolint/darwin-arm64': 7.0.2001
'@oxlint-tsgolint/darwin-x64': 7.0.2001
'@oxlint-tsgolint/linux-arm64': 7.0.2001
'@oxlint-tsgolint/linux-x64': 7.0.2001
'@oxlint-tsgolint/win32-arm64': 7.0.2001
'@oxlint-tsgolint/win32-x64': 7.0.2001
oxlint@1.71.0(oxlint-tsgolint@0.23.0):
oxlint@1.75.0(oxlint-tsgolint@7.0.2001):
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.71.0
'@oxlint/binding-android-arm64': 1.71.0
'@oxlint/binding-darwin-arm64': 1.71.0
'@oxlint/binding-darwin-x64': 1.71.0
'@oxlint/binding-freebsd-x64': 1.71.0
'@oxlint/binding-linux-arm-gnueabihf': 1.71.0
'@oxlint/binding-linux-arm-musleabihf': 1.71.0
'@oxlint/binding-linux-arm64-gnu': 1.71.0
'@oxlint/binding-linux-arm64-musl': 1.71.0
'@oxlint/binding-linux-ppc64-gnu': 1.71.0
'@oxlint/binding-linux-riscv64-gnu': 1.71.0
'@oxlint/binding-linux-riscv64-musl': 1.71.0
'@oxlint/binding-linux-s390x-gnu': 1.71.0
'@oxlint/binding-linux-x64-gnu': 1.71.0
'@oxlint/binding-linux-x64-musl': 1.71.0
'@oxlint/binding-openharmony-arm64': 1.71.0
'@oxlint/binding-win32-arm64-msvc': 1.71.0
'@oxlint/binding-win32-ia32-msvc': 1.71.0
'@oxlint/binding-win32-x64-msvc': 1.71.0
oxlint-tsgolint: 0.23.0
'@oxlint/binding-android-arm-eabi': 1.75.0
'@oxlint/binding-android-arm64': 1.75.0
'@oxlint/binding-darwin-arm64': 1.75.0
'@oxlint/binding-darwin-x64': 1.75.0
'@oxlint/binding-freebsd-x64': 1.75.0
'@oxlint/binding-linux-arm-gnueabihf': 1.75.0
'@oxlint/binding-linux-arm-musleabihf': 1.75.0
'@oxlint/binding-linux-arm64-gnu': 1.75.0
'@oxlint/binding-linux-arm64-musl': 1.75.0
'@oxlint/binding-linux-ppc64-gnu': 1.75.0
'@oxlint/binding-linux-riscv64-gnu': 1.75.0
'@oxlint/binding-linux-riscv64-musl': 1.75.0
'@oxlint/binding-linux-s390x-gnu': 1.75.0
'@oxlint/binding-linux-x64-gnu': 1.75.0
'@oxlint/binding-linux-x64-musl': 1.75.0
'@oxlint/binding-openharmony-arm64': 1.75.0
'@oxlint/binding-win32-arm64-msvc': 1.75.0
'@oxlint/binding-win32-ia32-msvc': 1.75.0
'@oxlint/binding-win32-x64-msvc': 1.75.0
oxlint-tsgolint: 7.0.2001
p-cancelable@2.1.1: {}
@ -12713,7 +12946,7 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vitest@4.1.5(@types/node@25.9.5)(happy-dom@20.9.0)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)):
vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(happy-dom@20.9.0)(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)):
dependencies:
'@vitest/expect': 4.1.5
'@vitest/mocker': 4.1.5(msw@2.14.3(@types/node@25.9.5)(typescript@7.0.2))(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
@ -12736,6 +12969,7 @@ snapshots:
vite: rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 25.9.5
happy-dom: 20.9.0
transitivePeerDependencies:
@ -12877,7 +13111,7 @@ snapshots:
zod@4.4.3: {}
zustand@5.0.13(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)):
zustand@5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)):
optionalDependencies:
'@types/react': 19.2.17
react: 19.2.7

View File

@ -1114,14 +1114,15 @@ const WorktreeCard = React.memo(function WorktreeCard({
},
[hoverReview, openTaskPage, repo]
)
const hoverReviewProvider = hoverReview?.provider
const hasExplicitLinkedReview =
(hoverReview?.provider === 'github' && worktree.linkedPR !== null) ||
(hoverReview?.provider === 'gitlab' && linkedGitLabMR !== null) ||
(hoverReview?.provider === 'bitbucket' && linkedBitbucketPR !== null) ||
(hoverReview?.provider === 'azure-devops' && linkedAzureDevOpsPR !== null) ||
(hoverReview?.provider === 'gitea' && linkedGiteaPR !== null)
(hoverReviewProvider === 'github' && worktree.linkedPR !== null) ||
(hoverReviewProvider === 'gitlab' && linkedGitLabMR !== null) ||
(hoverReviewProvider === 'bitbucket' && linkedBitbucketPR !== null) ||
(hoverReviewProvider === 'azure-devops' && linkedAzureDevOpsPR !== null) ||
(hoverReviewProvider === 'gitea' && linkedGiteaPR !== null)
const handleUnlinkReview = useCallback(() => {
switch (hoverReview?.provider) {
switch (hoverReviewProvider) {
case 'github':
void updateWorktreeMeta(worktree.id, { linkedPR: null })
return
@ -1136,12 +1137,12 @@ const WorktreeCard = React.memo(function WorktreeCard({
return
case 'gitea':
void updateWorktreeMeta(worktree.id, { linkedGiteaPR: null })
return
break
case 'unsupported':
case undefined:
break
}
}, [hoverReview?.provider, updateWorktreeMeta, worktree.id])
}, [hoverReviewProvider, updateWorktreeMeta, worktree.id])
const handleOpenLinearIssueInOrca = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation()