From 20794ee785b826480a927d06f2d134d806ddeae4 Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 14 Sep 2026 17:32:33 -0700
Subject: [PATCH 01/58] ci: keep the baseline build off the compatibility
matrix lanes (#20733)
The compatibility gate started the pinned 2.25.5 source build inside the same
step that runs the three measured lanes, so `make -j$(nproc)` competed with two
container lanes whose wall clock is container starts, not Git. A boundary case
that costs ~1.5s stretched past Vitest's 30s timeout and failed the job.
Build the binary in its own step before the matrix, and pull both images before
any lane starts so a lazy pull cannot stall whichever test its sibling is timing.
---
.github/workflows/pr.yml | 57 ++++++++-----
...git-binary-compatibility-workflow.test.mjs | 79 ++++++++++++-------
docs/reference/git-compatibility.md | 6 ++
3 files changed, 94 insertions(+), 48 deletions(-)
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 398bd61bd04..2f54f2ae785 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -326,40 +326,59 @@ jobs:
# Why: the 2.25.5 lane is a source build of a pinned tarball, so it produced the
# same binary on every PR for minutes of runner time. The key carries the version
# because that is the only input; the sha256 assertion below still guards the
- # tarball on the miss path that actually builds.
+ # tarball on the miss path that actually builds. Only this PR's own later pushes
+ # can restore it — GitHub scopes a cache written from a pull_request run to that
+ # ref — so a first push always takes the build path below.
- name: Cache baseline Git build
uses: actions/cache@v5
with:
path: ~/.cache/orca-git-compat/git-2.25.5
key: git-compat-baseline-${{ runner.os }}-${{ runner.arch }}-2.25.5
+ # Why its own step: this is `make -j$(nproc)` on every core, and the lanes below
+ # spend their wall clock waiting on container starts, not on Git. Sharing a runner
+ # with the build stretched one ~1.5s boundary case past Vitest's 30s timeout, so
+ # the build has to finish before anything timed starts.
+ - name: Build the baseline Git binary
+ run: |
+ archive="$RUNNER_TEMP/git-2.25.5.tar.gz"
+ source="$HOME/.cache/orca-git-compat/git-2.25.5"
+ if [ -x "$source/git" ]; then
+ exit 0
+ fi
+ curl -fsSL https://www.kernel.org/pub/software/scm/git/git-2.25.5.tar.gz -o "$archive"
+ echo "41662c52fc16fec4963bfc41075e71f8ead6b5e386797eb6f9a1111ff95a8ddf $archive" \
+ | sha256sum --check
+ mkdir -p "$source"
+ tar -xzf "$archive" -C "$source" --strip-components=1
+ make -C "$source" -j"$(nproc)" \
+ NO_GETTEXT=YesPlease NO_TCLTK=YesPlease NO_PYTHON=YesPlease git
+ # Why: the linked binaries are what the next run needs; the objects that
+ # produced them are most of the tree and would bloat the cache entry.
+ find "$source" -name '*.o' -delete
+
- name: Verify Git binary compatibility matrix
run: |
+ specs=(
+ "alpine/git:edge-2.38.1|2.38.1"
+ "alpine/git:v2.49.1|2.49.1"
+ )
+ # Why pull up front: a lane's first `docker run` otherwise pulls its image
+ # while the sibling lane is mid-test, and that stall is charged to the test.
+ for spec in "${specs[@]}"; do
+ docker pull --quiet "${spec%%|*}"
+ done
+
pids=()
(
- archive="$RUNNER_TEMP/git-2.25.5.tar.gz"
- source="$HOME/.cache/orca-git-compat/git-2.25.5"
- if [ ! -x "$source/git" ]; then
- curl -fsSL https://www.kernel.org/pub/software/scm/git/git-2.25.5.tar.gz -o "$archive"
- echo "41662c52fc16fec4963bfc41075e71f8ead6b5e386797eb6f9a1111ff95a8ddf $archive" \
- | sha256sum --check
- mkdir -p "$source"
- tar -xzf "$archive" -C "$source" --strip-components=1
- make -C "$source" -j"$(nproc)" \
- NO_GETTEXT=YesPlease NO_TCLTK=YesPlease NO_PYTHON=YesPlease git
- # Why: the linked binaries are what the next run needs; the objects that
- # produced them are most of the tree and would bloat the cache entry.
- find "$source" -name '*.o' -delete
- fi
- ORCA_GIT_COMPAT_BINARY="$source/git" ORCA_GIT_COMPAT_VERSION="2.25.5" \
+ ORCA_GIT_COMPAT_BINARY="$HOME/.cache/orca-git-compat/git-2.25.5/git" \
+ ORCA_GIT_COMPAT_VERSION="2.25.5" \
pnpm exec vitest run --config config/vitest.config.ts \
src/shared/git-binary-compatibility.test.ts
) &
pids+=("$!")
- for spec in \
- "alpine/git:edge-2.38.1|2.38.1" \
- "alpine/git:v2.49.1|2.49.1"; do
+ for spec in "${specs[@]}"; do
(
image="${spec%%|*}"
version="${spec#*|}"
diff --git a/config/scripts/git-binary-compatibility-workflow.test.mjs b/config/scripts/git-binary-compatibility-workflow.test.mjs
index afe5615bb44..35d2b5c60dc 100644
--- a/config/scripts/git-binary-compatibility-workflow.test.mjs
+++ b/config/scripts/git-binary-compatibility-workflow.test.mjs
@@ -2,42 +2,63 @@ import { readFileSync } from 'node:fs'
import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
+const BASELINE_DIR = '~/.cache/orca-git-compat/git-2.25.5'
+
+const gateSteps = () =>
+ parse(readFileSync('.github/workflows/pr.yml', 'utf8')).jobs.git_compatibility.steps
+
+const stepNamed = (name) => gateSteps().find((step) => step.name === name)
+
describe('Git binary compatibility PR gate', () => {
it('runs the real-binary contract at each compatibility boundary', () => {
- const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
- const step = workflow.jobs.git_compatibility.steps.find(
- (candidate) => candidate.name === 'Verify Git binary compatibility matrix'
- )
+ const run = stepNamed('Verify Git binary compatibility matrix')?.run
- expect(step?.run).toContain('git-2.25.5.tar.gz')
+ expect(run).toContain('ORCA_GIT_COMPAT_BINARY="$HOME/.cache/orca-git-compat/git-2.25.5/git"')
+ expect(run).toContain('alpine/git:edge-2.38.1|2.38.1')
+ expect(run).toContain('alpine/git:v2.49.1|2.49.1')
+ expect(run).toContain('ORCA_GIT_COMPAT_IMAGE="$image"')
+ expect(run).toContain('src/shared/git-binary-compatibility.test.ts')
+ expect(run).toContain('pids+=("$!")')
+ expect(run).toContain('wait "$pid" || status=1')
+ })
+
+ it('builds the pinned baseline tarball into the cached directory', () => {
+ const run = stepNamed('Build the baseline Git binary')?.run
+
+ expect(run).toContain('git-2.25.5.tar.gz')
// Why asserted: the sha256 check only runs on the build path, so a cached binary
// must come from a key that pins the same version the tarball line declares.
- expect(step?.run).toContain('if [ ! -x "$source/git" ]; then')
- expect(step?.run).toContain('41662c52fc16fec4963bfc41075e71f8ead6b5e386797eb6f9a1111ff95a8ddf')
- expect(step?.run).toContain('ORCA_GIT_COMPAT_BINARY="$source/git"')
- expect(step?.run).toContain('alpine/git:edge-2.38.1|2.38.1')
- expect(step?.run).toContain('alpine/git:v2.49.1|2.49.1')
- expect(step?.run).toContain('ORCA_GIT_COMPAT_IMAGE="$image"')
- expect(step?.run).toContain('src/shared/git-binary-compatibility.test.ts')
- expect(step?.run).toContain('-j"$(nproc)"')
- expect(step?.run).toContain('pids+=("$!")')
- expect(step?.run).toContain('wait "$pid" || status=1')
- })
-
- it('restores the baseline Git build before the matrix runs', () => {
- const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
- const steps = workflow.jobs.git_compatibility.steps
- const cacheIndex = steps.findIndex((step) => step.name === 'Cache baseline Git build')
- const matrixIndex = steps.findIndex(
- (step) => step.name === 'Verify Git binary compatibility matrix'
- )
-
- expect(cacheIndex).toBeGreaterThanOrEqual(0)
- expect(cacheIndex).toBeLessThan(matrixIndex)
+ expect(run).toContain('if [ -x "$source/git" ]; then')
+ expect(run).toContain('41662c52fc16fec4963bfc41075e71f8ead6b5e386797eb6f9a1111ff95a8ddf')
+ expect(run).toContain('-j"$(nproc)"')
// The cached path and the build path must be the same directory or the guard
// above would rebuild on every run while still reporting a cache hit.
- expect(steps[cacheIndex].with.path).toBe('~/.cache/orca-git-compat/git-2.25.5')
- expect(steps[matrixIndex].run).toContain('source="$HOME/.cache/orca-git-compat/git-2.25.5"')
+ expect(run).toContain('source="$HOME/.cache/orca-git-compat/git-2.25.5"')
+ })
+
+ it('finishes the baseline build before the timed lanes start', () => {
+ const steps = gateSteps()
+ const names = steps.map((step) => step.name)
+ const cacheIndex = names.indexOf('Cache baseline Git build')
+ const buildIndex = names.indexOf('Build the baseline Git binary')
+ const matrixIndex = names.indexOf('Verify Git binary compatibility matrix')
+
+ expect(cacheIndex).toBeGreaterThanOrEqual(0)
+ expect(cacheIndex).toBeLessThan(buildIndex)
+ expect(buildIndex).toBeLessThan(matrixIndex)
+ // Why asserted: each lane is bounded by Vitest's per-test timeout while it waits on
+ // container starts, so a `make -j$(nproc)` sharing the runner shows up as a timeout
+ // in whichever boundary case is running rather than as a slow build.
+ expect(steps[matrixIndex].run).not.toContain('make -C')
+ expect(steps[cacheIndex].with.path).toBe(BASELINE_DIR)
expect(steps[cacheIndex].with.key).toContain('2.25.5')
})
+
+ it('pulls every matrix image before any lane runs', () => {
+ const run = stepNamed('Verify Git binary compatibility matrix')?.run
+ // A lazy pull inside one lane stalls whatever test the sibling lane is timing.
+ const [beforeLanes] = run.split('pids=()')
+
+ expect(beforeLanes).toContain('docker pull --quiet "${spec%%|*}"')
+ })
})
diff --git a/docs/reference/git-compatibility.md b/docs/reference/git-compatibility.md
index 1e19860385e..d537c4d94de 100644
--- a/docs/reference/git-compatibility.md
+++ b/docs/reference/git-compatibility.md
@@ -69,6 +69,12 @@ PR checks run the capability contract against real Git 2.25.5, 2.38.1, and
2.49.1 binaries. This spans the pre-2.29 serialized `FETCH_HEAD` fallback, the transitional
`merge-tree --write-tree` behavior before `--merge-base`, and current Git.
+The three lanes run in parallel and each Git call in the container lanes costs a
+container start, so their wall clock is runner contention, not Git. Build the
+2.25.5 binary and pull the images before the lanes start: anything heavy left
+running alongside them is charged to whichever boundary case is in flight and
+surfaces as a Vitest timeout rather than as a slow setup step.
+
Keep the unit tests alongside that matrix. They cover concurrent probes,
native/WSL/SSH/relay isolation, and error-stream shapes that a single real
binary invocation cannot exercise deterministically.
From 2b34255d9657dc63830978f4e4433a422ec30765 Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 14 Sep 2026 17:47:11 -0700
Subject: [PATCH 02/58] fix(ci): stop defining pilot mutant tests inside a
conditional (#20755)
`vitest/no-conditional-tests` fires on the `if (mutation) { it(...) }` inside
the pilot loop, and `audit:code-quality:native` runs oxlint with
`--deny-warnings`, so main's "Enforce focused code-quality plugins" step exits
1 and blocks every open PR.
Pair each pilot with its pinned mutant and reference state before the loops, so
every iteration defines exactly one test unconditionally. Same 14 tests, same
names: 11 mutant-kill tests and the 3 reference tests that `skipIf` still gates
on RPC_FOUNDATION_REFERENCE_ROOT.
---
.../mutants/pilot-mutants.test.ts | 49 ++++++++++---------
1 file changed, 27 insertions(+), 22 deletions(-)
diff --git a/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts b/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts
index a81a48238f5..acd9840e4f0 100644
--- a/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts
+++ b/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts
@@ -59,30 +59,35 @@ function visibleState(recording: Recording): RecordedValue {
return recording.checkpoints.at(-1)!.observation.state
}
+// Pair pilots with their pinned mutant/reference up front so each loop below defines exactly one test.
+const pilots = pilotGoldens(input.scenarios)
+const mutantPilots = pilots.flatMap((pilot) => {
+ const mutation = mutants[pilot.id]
+ return mutation ? [{ ...pilot, mutation }] : []
+})
+const referencePilots = pilots.flatMap((pilot) => {
+ const reference = referenceStates[pilot.id]
+ return reference ? [{ ...pilot, reference }] : []
+})
+
describe('RPC main recording mutants', () => {
- for (const pilot of pilotGoldens(input.scenarios)) {
- const { id, scenario } = pilot
- const mutation = mutants[id]
- if (mutation) {
- it(`${id}: kills ${mutation}`, async () => {
- const { adapters, assertMutationApplied } = pilotMountAdapters(root, {
- mutation: operationMutation(mutation)
- })
- const result = await runRecordingMutant(
- scenario,
- adapters[scenario.operation],
- vitestRecordingScheduler(),
- readGolden(goldens, id).recording,
- visibleState
- )
- assertMutationApplied()
- expect(result.verdict).toBe('killed')
+ for (const { id, scenario, mutation } of mutantPilots) {
+ it(`${id}: kills ${mutation}`, async () => {
+ const { adapters, assertMutationApplied } = pilotMountAdapters(root, {
+ mutation: operationMutation(mutation)
})
- }
- const reference = referenceStates[id]
- if (!reference) {
- continue
- }
+ const result = await runRecordingMutant(
+ scenario,
+ adapters[scenario.operation],
+ vitestRecordingScheduler(),
+ readGolden(goldens, id).recording,
+ visibleState
+ )
+ assertMutationApplied()
+ expect(result.verdict).toBe('killed')
+ })
+ }
+ for (const { id, scenario, reference } of referencePilots) {
it.skipIf(!process.env.RPC_FOUNDATION_REFERENCE_ROOT)(`${id}: rejects bcba08b3e4`, async () => {
const { adapters } = pilotMountAdapters(process.env.RPC_FOUNDATION_REFERENCE_ROOT!, {
reference: true
From db09a7bd508fded16cd44a1925d432cdfc22131c Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Mon, 14 Sep 2026 17:47:19 -0700
Subject: [PATCH 03/58] fix(native-chat): let a reader park just above the
latest message (#20709)
* fix(native-chat): let a reader park just above the latest message
A reader who scrolled up by less than the bottom threshold was still
classified as being at the end, so follow stayed armed and the next chunk
of stream carried them back down. One constant was answering two
different questions: how close to the end still counts as pinned, and
whether a reader's own scroll meant to stay there.
The first wants slack, because a streaming last message jitters in height
by tens of pixels. The second wants almost none, because it is a
statement of intent. Give it its own, far stricter band, and move the
choice of band into the decision rather than leaving it to the call site,
which is where the two got conflated.
Re-arming follow now requires the reader to be within 4px of the end:
enough for fractional-pixel and zoom rounding, well inside one line of
prose. The pin and the jump-to-latest affordance keep their 48px band.
* fix(native-chat): make transcript intent own end following
---
.../native-chat/NativeChatMessageList.tsx | 5 +-
.../NativeChatMessageList.windowing.test.tsx | 185 +++++++++++++++++-
.../native-chat-autoscroll.test.ts | 52 ++++-
.../native-chat/native-chat-autoscroll.ts | 17 +-
.../use-native-chat-transcript-scroll.ts | 3 +-
...ve-chat-transcript-window.options.test.tsx | 6 +-
.../use-native-chat-transcript-window.ts | 17 +-
7 files changed, 248 insertions(+), 37 deletions(-)
diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx
index 108a188c4ab..f7462e07434 100644
--- a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx
@@ -256,10 +256,7 @@ export function NativeChatMessageList({
// Named so measurement can find the scroll root without depending on
// which utility class happens to make it scroll.
data-native-chat-scroll
- // `overflow-anchor:none`: the transcript decides whether an offset
- // it did not write is the reader moving, so the engine adjusting
- // scrollTop under a settling row would read as a departure. The
- // virtualizer does its own end anchoring, so this is redundant here.
+ // Browser anchoring would add unattributed movement beside the virtualizer's anchor.
className="scrollbar-sleek relative h-full overflow-y-auto [overflow-anchor:none] [scrollbar-gutter:stable_both-edges]"
// Why: `zoom` scales the chat transcript's text and layout together,
// scoped to this pane so the rest of the app is untouched. It sits on
diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx
index 8d9f3ffa83b..e4debc8e676 100644
--- a/src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatMessageList.windowing.test.tsx
@@ -12,7 +12,10 @@ import { projectStructuredItemsToNativeChat } from '../../../../shared/structure
import type { NativeChatMessage } from '../../../../shared/native-chat-types'
import type { NativeChatLiveSession } from './use-native-chat-live-session'
import { NativeChatMessageList } from './NativeChatMessageList'
-import { NATIVE_CHAT_BOTTOM_THRESHOLD_PX } from './native-chat-autoscroll'
+import {
+ NATIVE_CHAT_BOTTOM_THRESHOLD_PX,
+ NATIVE_CHAT_FOLLOW_REARM_PX
+} from './native-chat-autoscroll'
import {
estimateNativeChatRowHeight,
NATIVE_CHAT_ROW_GAP_PX,
@@ -469,11 +472,8 @@ describe('transcript with a hidden scroll root', () => {
// arrive at their final height and are a different case; this is the one where
// the row the reader is looking at keeps changing size underneath them.
//
-// Two mechanisms are supposed to hold the pin, and both are exercised here: the
-// list's own resize observer on the transcript column (which re-runs
-// `scrollToBottom` against the document) and the virtualizer's end anchor (which
-// compensates `scrollTop` by the growth when the view was already at the end).
-describe('a row growing in place while the view is pinned to the bottom', () => {
+// Exercise the real virtualizer together with the transcript's follow owner.
+describe('transcript follow ownership across growth and appends', () => {
const TAIL_INDEX = TRANSCRIPT_LENGTH - 1
const GROWTH_STEPS = 24
const LINES_PER_STEP = 12
@@ -490,6 +490,13 @@ describe('a row growing in place while the view is pinned to the bottom', () =>
const transcript = Array.from({ length: TRANSCRIPT_LENGTH }, (_, index) => marker(index))
+ function appendedTranscript(count: number): NativeChatMessage[] {
+ return [
+ ...transcript,
+ ...Array.from({ length: count }, (_, index) => marker(TRANSCRIPT_LENGTH + index))
+ ]
+ }
+
function tailHeightAt(step: number): number {
return Math.max(ROW_PX, (1 + step * LINES_PER_STEP) * STREAM_LINE_PX)
}
@@ -642,6 +649,170 @@ describe('a row growing in place while the view is pinned to the bottom', () =>
expect(screen.getByRole('button', { name: /jump to latest/i })).toBeInTheDocument()
})
+ it.each([0, 100])(
+ 'keeps a reader parked above a growing row with a %i px initial measurement delta',
+ (measurementDelta) => {
+ setMeasuredTail(4)
+ measuredRowHeights = measuredRowHeights.map((height, index) =>
+ index === TAIL_INDEX ? height + measurementDelta : height
+ )
+ const { container, rerender } = render(streamingList(4))
+ paint(container)
+ const scroller = scrollRoot(container)
+
+ const parkGapPx = NATIVE_CHAT_BOTTOM_THRESHOLD_PX - 8
+ const parkedAt = scroller.scrollHeight - scroller.clientHeight - parkGapPx
+ scrollTranscript(container, parkedAt)
+ expect(distanceFromBottom(container)).toBe(parkGapPx)
+ // Not the "scrolled far away" case above: the latest message is still on
+ // screen, so there is nothing to offer a way back to yet.
+ expect(screen.queryByRole('button', { name: /jump to latest/i })).toBeNull()
+
+ setMeasuredTail(5)
+ rerender(streamingList(5))
+ paint(container)
+ expect(scroller.scrollTop).toBe(parkedAt)
+
+ let previousDistance = distanceFromBottom(container)
+ for (let step = 6; step <= GROWTH_STEPS; step += 1) {
+ setMeasuredTail(step)
+ rerender(streamingList(step))
+ paint(container)
+
+ // The offset stops moving at all...
+ expect(scroller.scrollTop).toBe(parkedAt)
+ // ...so the end runs away from the reader instead of carrying them along.
+ const distance = distanceFromBottom(container)
+ expect(distance).toBeGreaterThan(previousDistance)
+ previousDistance = distance
+ }
+
+ expect(previousDistance).toBeGreaterThan(VIEWPORT_PX)
+ expect(screen.getByRole('button', { name: /jump to latest/i })).toBeInTheDocument()
+ }
+ )
+
+ it('leaves a parked reader in place through repeated appends', () => {
+ const { container, rerender } = render(list(transcript))
+ paint(container)
+ const scroller = scrollRoot(container)
+ const parkedAt = scroller.scrollHeight - scroller.clientHeight - 40
+ scrollTranscript(container, parkedAt)
+
+ for (let count = 1; count <= 8; count += 1) {
+ rerender(list(appendedTranscript(count)))
+ paint(container)
+ expect(scroller.scrollTop).toBe(parkedAt)
+ expect(windowState(container).indexes.length).toBeLessThan(TRANSCRIPT_LENGTH / 4)
+ }
+ expect(screen.getByRole('button', { name: /jump to latest/i })).toBeInTheDocument()
+ })
+
+ it('follows repeated appends until the reader detaches', () => {
+ const { container, rerender } = render(list(transcript))
+ paint(container)
+ const scroller = scrollRoot(container)
+ for (let count = 1; count <= 8; count += 1) {
+ rerender(list(appendedTranscript(count)))
+ paint(container)
+ expect(distanceFromBottom(container)).toBeLessThanOrEqual(NATIVE_CHAT_FOLLOW_REARM_PX)
+ fireEvent.scroll(scroller)
+ }
+
+ const parkedAt = scroller.scrollTop - 22
+ scrollTranscript(container, parkedAt)
+ rerender(list(appendedTranscript(9)))
+ paint(container)
+ expect(scroller.scrollTop).toBe(parkedAt)
+ })
+
+ it('follows an empty transcript through underflow into scrollable output', () => {
+ const { container, rerender } = render(list([]))
+ paint(container)
+ expect(scrollRoot(container).scrollTop).toBe(0)
+ rerender(list(transcript.slice(0, 1)))
+ paint(container)
+ expect(scrollRoot(container).scrollTop).toBe(0)
+ fireEvent.scroll(scrollRoot(container))
+ rerender(list(transcript))
+ paint(container)
+ expect(distanceFromBottom(container)).toBeLessThanOrEqual(NATIVE_CHAT_FOLLOW_REARM_PX)
+ expect(windowState(container).indexes.length).toBeLessThan(TRANSCRIPT_LENGTH / 4)
+ })
+
+ it.each(['reader', 'jump'] as const)('rearms growth and append following via %s', (rearm) => {
+ setMeasuredTail(4)
+ const { container, rerender } = render(streamingList(4))
+ paint(container)
+ const scroller = scrollRoot(container)
+ fireEvent.scroll(scroller)
+ const parkedAt = scroller.scrollTop - 22
+ scrollTranscript(container, parkedAt)
+ setMeasuredTail(5)
+ rerender(streamingList(5))
+ paint(container)
+ expect(scroller.scrollTop).toBe(parkedAt)
+
+ if (rearm === 'reader') {
+ scrollTranscript(
+ container,
+ scroller.scrollHeight - scroller.clientHeight - NATIVE_CHAT_FOLLOW_REARM_PX
+ )
+ } else {
+ fireEvent.click(screen.getByRole('button', { name: /jump to latest/i }))
+ }
+ paint(container)
+ expect(screen.queryByRole('button', { name: /jump to latest/i })).toBeNull()
+ for (let step = 6; step <= 8; step += 1) {
+ setMeasuredTail(step)
+ rerender(streamingList(step))
+ paint(container)
+ expect(distanceFromBottom(container)).toBeLessThanOrEqual(NATIVE_CHAT_FOLLOW_REARM_PX)
+ expect(windowState(container).indexes.length).toBeLessThan(TRANSCRIPT_LENGTH / 4)
+ }
+ rerender(list([...transcriptAt(8), marker(TRANSCRIPT_LENGTH)]))
+ paint(container)
+ expect(distanceFromBottom(container)).toBeLessThanOrEqual(NATIVE_CHAT_FOLLOW_REARM_PX)
+ })
+
+ it('preserves the visible row anchor across prepends while detached', () => {
+ const { container, rerender } = render(list(transcript))
+ paint(container)
+ const readingAt = 2000
+ scrollTranscript(container, readingAt)
+ paint(container)
+
+ const earlier = Array.from({ length: 10 }, (_, index) => marker(index - 10))
+ rerender(list([...earlier, ...transcript]))
+ paint(container)
+ expect(scrollRoot(container).scrollTop).toBe(readingAt + earlier.length * ROW_PITCH_PX)
+ expect(windowState(container).indexes.length).toBeLessThan(TRANSCRIPT_LENGTH / 4)
+ expect(screen.getByRole('button', { name: /jump to latest/i })).toBeInTheDocument()
+ })
+
+ it('compensates a measurement entirely above the viewport without reattaching', () => {
+ const { container, rerender } = render(list(transcript))
+ paint(container)
+ const scroller = scrollRoot(container)
+ fireEvent.scroll(scroller)
+ const readingAt = 2000
+ scrollTranscript(container, readingAt)
+ paint(container)
+ const aboveIndex = windowState(container).indexes[0]!
+ expect((aboveIndex + 1) * ROW_PITCH_PX).toBeLessThan(readingAt)
+ for (const growth of [100, 200]) {
+ measuredRowHeights = Array.from({ length: TRANSCRIPT_LENGTH }, (_, index) =>
+ index === aboveIndex ? ROW_PX + growth : ROW_PX
+ )
+ paint(container)
+ expect(scroller.scrollTop).toBe(readingAt + growth)
+ }
+ rerender(list(appendedTranscript(1)))
+ paint(container)
+ expect(scroller.scrollTop).toBe(readingAt + 200)
+ expect(windowState(container).indexes.length).toBeLessThan(TRANSCRIPT_LENGTH / 4)
+ })
+
it('keeps following when a pin echo arrives after the document grows', () => {
setMeasuredTail(0)
const { container } = render(streamingList(0))
@@ -657,6 +828,8 @@ describe('a row growing in place while the view is pinned to the bottom', () =>
expect(scroller.scrollTop).toBe(pinnedAt)
expect(screen.queryByRole('button', { name: /jump to latest/i })).toBeNull()
+ paint(container)
+ expect(distanceFromBottom(container)).toBeLessThanOrEqual(NATIVE_CHAT_FOLLOW_REARM_PX)
})
it('settles a pending end reconcile after the reader keeps scrolling away', async () => {
diff --git a/src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts b/src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts
index fa23669e931..0dc34b7eb30 100644
--- a/src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts
+++ b/src/renderer/src/components/native-chat/native-chat-autoscroll.test.ts
@@ -5,13 +5,23 @@ import {
nextFollowingEnd,
shouldLoadEarlier,
shouldShowJumpToLatest,
- NATIVE_CHAT_BOTTOM_THRESHOLD_PX
+ NATIVE_CHAT_BOTTOM_THRESHOLD_PX,
+ NATIVE_CHAT_FOLLOW_REARM_PX
} from './native-chat-autoscroll'
const atBottom = { scrollTop: 952, scrollHeight: 1000, clientHeight: 48 }
const scrolledUp = { scrollTop: 0, scrollHeight: 1000, clientHeight: 48 }
const noOverflow = { scrollTop: 0, scrollHeight: 48, clientHeight: 48 }
+/** A view parked exactly `distance` px above the end of the same document. */
+function parkedAbove(distance: number): {
+ scrollTop: number
+ scrollHeight: number
+ clientHeight: number
+} {
+ return { scrollTop: 952 - distance, scrollHeight: 1000, clientHeight: 48 }
+}
+
describe('distanceFromBottom', () => {
it('is zero at the exact bottom and never negative', () => {
expect(distanceFromBottom(atBottom)).toBe(0)
@@ -48,7 +58,8 @@ describe('shouldShowJumpToLatest', () => {
// The browser reports application writes as ordinary scroll events. Explicit
// marks distinguish their delayed echoes from reader movement after growth.
describe('nextFollowingEnd', () => {
- const following = { following: true, programmatic: false, atEnd: true }
+ const following = { following: true, programmatic: false, geometry: parkedAbove(0) }
+ const wellAway = parkedAbove(400)
it('follows when the reader reaches the end', () => {
expect(nextFollowingEnd(following)).toBe(true)
@@ -58,15 +69,44 @@ describe('nextFollowingEnd', () => {
// the end runs away from an offset the transcript itself pinned. That is not a
// reader leaving, and treating it as one strands them mid-transcript.
it('keeps following when a delayed application scroll arrives after growth', () => {
- expect(nextFollowingEnd({ ...following, programmatic: true, atEnd: false })).toBe(true)
+ expect(nextFollowingEnd({ ...following, programmatic: true, geometry: wellAway })).toBe(true)
})
it('treats an unmarked offset away from the end as the reader leaving', () => {
- expect(nextFollowingEnd({ ...following, atEnd: false })).toBe(false)
+ expect(nextFollowingEnd({ ...following, geometry: wellAway })).toBe(false)
})
- it('does not re-attach a detached reader from an application write', () => {
- expect(nextFollowingEnd({ following: false, programmatic: true, atEnd: false })).toBe(false)
+ it.each([0, NATIVE_CHAT_FOLLOW_REARM_PX, 400])(
+ 'does not reattach a detached reader from an application write %i px from the end',
+ (distance) => {
+ expect(
+ nextFollowingEnd({ following: false, programmatic: true, geometry: parkedAbove(distance) })
+ ).toBe(false)
+ }
+ )
+
+ // The jump affordance's wider band must not decide whether a reader follows.
+ it('lets the reader park just inside the near-bottom band', () => {
+ expect(NATIVE_CHAT_FOLLOW_REARM_PX).toBeLessThan(NATIVE_CHAT_BOTTOM_THRESHOLD_PX)
+ const parked = parkedAbove(NATIVE_CHAT_BOTTOM_THRESHOLD_PX - 1)
+ expect(nextFollowingEnd({ ...following, geometry: parked })).toBe(false)
+ expect(isNearBottom(parked)).toBe(true)
+ expect(shouldShowJumpToLatest(false, parked)).toBe(false)
+ })
+
+ it('re-arms at the band and not one pixel past it', () => {
+ const detached = { following: false, programmatic: false }
+ expect(
+ nextFollowingEnd({ ...detached, geometry: parkedAbove(NATIVE_CHAT_FOLLOW_REARM_PX) })
+ ).toBe(true)
+ expect(
+ nextFollowingEnd({ ...detached, geometry: parkedAbove(NATIVE_CHAT_FOLLOW_REARM_PX + 1) })
+ ).toBe(false)
+ })
+
+ // Sub-pixel and zoom rounding put the true end a fraction short of exact.
+ it('holds follow through rounding noise at the end', () => {
+ expect(nextFollowingEnd({ ...following, geometry: parkedAbove(1.5) })).toBe(true)
})
})
diff --git a/src/renderer/src/components/native-chat/native-chat-autoscroll.ts b/src/renderer/src/components/native-chat/native-chat-autoscroll.ts
index f07aeeb6671..a8f2e54b22f 100644
--- a/src/renderer/src/components/native-chat/native-chat-autoscroll.ts
+++ b/src/renderer/src/components/native-chat/native-chat-autoscroll.ts
@@ -11,9 +11,7 @@ export type ScrollGeometry = {
clientHeight: number
}
-/** Pixels from the bottom within which we treat the view as "at the bottom" and
- * keep it pinned as content arrives. A small slack absorbs sub-pixel rounding
- * and the height jitter of a streaming last message. */
+/** Hide the jump affordance while the latest output is still nearby. */
export const NATIVE_CHAT_BOTTOM_THRESHOLD_PX = 48
/** Distance in px from the bottom edge of the scroll range. */
@@ -21,8 +19,7 @@ export function distanceFromBottom(geometry: ScrollGeometry): number {
return Math.max(0, geometry.scrollHeight - geometry.clientHeight - geometry.scrollTop)
}
-/** True when the viewport is close enough to the bottom that new content should
- * keep it pinned (auto-scroll "attached"). */
+/** Whether the viewport is inside the requested distance from the bottom. */
export function isNearBottom(
geometry: ScrollGeometry,
threshold: number = NATIVE_CHAT_BOTTOM_THRESHOLD_PX
@@ -43,22 +40,26 @@ export function shouldShowJumpToLatest(
return distanceFromBottom(geometry) > threshold
}
+/** Allow bottom rounding noise without following a reader who moved up a line. */
+export const NATIVE_CHAT_FOLLOW_REARM_PX = 4
+
export type FollowIntent = {
following: boolean
/** Whether the scroll event matches an offset the application registered. */
programmatic: boolean
- atEnd: boolean
+ geometry: ScrollGeometry
}
/** Whether the transcript should still follow the end after this offset.
*
* Application writes preserve intent even when their delayed events arrive
- * after the end moved. Reader events detach away from the end and reattach at it. */
+ * after the end moved. Reader events detach away from the end and reattach at
+ * it — against the re-arm band, never the wider near-bottom one. */
export function nextFollowingEnd(intent: FollowIntent): boolean {
if (intent.programmatic) {
return intent.following
}
- return intent.atEnd
+ return isNearBottom(intent.geometry, NATIVE_CHAT_FOLLOW_REARM_PX)
}
/** Distance from the top within which the transcript pages in older history. */
diff --git a/src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts b/src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
index 0f690bce57e..c54cbf54e85 100644
--- a/src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
+++ b/src/renderer/src/components/native-chat/use-native-chat-transcript-scroll.ts
@@ -21,7 +21,6 @@ import {
type UIEventHandler
} from 'react'
import {
- isNearBottom,
nextFollowingEnd,
shouldLoadEarlier,
shouldShowJumpToLatest,
@@ -89,7 +88,7 @@ export function useNativeChatTranscriptScroll({
const following = nextFollowingEnd({
following: followingRef.current,
programmatic,
- atEnd: isNearBottom(geometry)
+ geometry
})
followingRef.current = following
if (!programmatic) {
diff --git a/src/renderer/src/components/native-chat/use-native-chat-transcript-window.options.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-transcript-window.options.test.tsx
index da19a51a9c3..82166952670 100644
--- a/src/renderer/src/components/native-chat/use-native-chat-transcript-window.options.test.tsx
+++ b/src/renderer/src/components/native-chat/use-native-chat-transcript-window.options.test.tsx
@@ -67,7 +67,7 @@ afterEach(() => {
})
describe('native chat transcript virtualizer contract', () => {
- it('configures prepend anchoring and matching bottom-follow behavior', () => {
+ it('retains prepend anchoring without independently following the end', () => {
renderHook(() =>
useNativeChatTranscriptWindow({
scrollRef: { current: null },
@@ -78,8 +78,8 @@ describe('native chat transcript virtualizer contract', () => {
expect(virtualizerMock.options.current).toMatchObject({
anchorTo: 'end',
- followOnAppend: true,
- scrollEndThreshold: 48
+ followOnAppend: false,
+ scrollEndThreshold: -1
})
})
diff --git a/src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts b/src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts
index 24b63e1fbae..30227bf47c5 100644
--- a/src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts
+++ b/src/renderer/src/components/native-chat/use-native-chat-transcript-window.ts
@@ -1,11 +1,8 @@
// DOM windowing for the transcript: only the rows near the viewport are mounted,
// the rest are reserved as estimated height.
//
-// Anchoring is the library's, not ours. `anchorTo: 'end'` captures the row at the
-// current offset before a count change and re-resolves its position afterwards,
-// which is what keeps a "load earlier" prepend from yanking the view;
-// `followOnAppend` + `scrollEndThreshold` keep a reader who is already at the
-// bottom pinned there as a turn streams.
+// The virtualizer owns visible-row anchoring; the transcript scroll hook owns
+// end-follow intent. Geometry alone must never reattach a parked reader.
//
// Every measurement here ends up in the scroll container's own coordinate space,
// which means `offsetTop` / `offsetHeight` rather than a bounding rect. The
@@ -16,7 +13,6 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { elementScroll, useVirtualizer, type VirtualItem } from '@tanstack/react-virtual'
import { createProgrammaticScrollMarks } from '@/hooks/programmatic-scroll-marks'
-import { NATIVE_CHAT_BOTTOM_THRESHOLD_PX } from './native-chat-autoscroll'
import { NATIVE_CHAT_ROW_GAP_PX } from './native-chat-row-height-estimate'
import { nativeChatPinnedRowIndexes, nativeChatTranscriptRange } from './native-chat-pinned-rows'
import type { NativeChatTranscriptSlot } from './native-chat-transcript-slots'
@@ -135,8 +131,9 @@ export function useNativeChatTranscriptWindow({
gap: NATIVE_CHAT_ROW_GAP_PX,
scrollMargin,
anchorTo: 'end',
- followOnAppend: true,
- scrollEndThreshold: NATIVE_CHAT_BOTTOM_THRESHOLD_PX,
+ followOnAppend: false,
+ // Distances are nonnegative: disable geometry-only resize pinning, retaining prepend anchoring.
+ scrollEndThreshold: -1,
// Every virtualizer write uses this public adapter, including measurement
// adjustments and prepend anchoring, so scroll events have one provenance.
scrollToFn: (offset, options, instance) => {
@@ -164,6 +161,10 @@ export function useNativeChatTranscriptWindow({
}
})
+ // Growing a row that spans the viewport changes content below the reader's anchor.
+ virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (item, _delta, instance) =>
+ item.end <= (instance.scrollOffset ?? 0)
+
const finishReaderTakeover = useCallback(() => {
if (readerTakeoverFrameRef.current !== null) {
window.cancelAnimationFrame(readerTakeoverFrameRef.current)
From b61a2347b99cc5d6c001473108c621811a35e33b Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 14 Sep 2026 17:52:21 -0700
Subject: [PATCH 04/58] feat(design-system): gate renderer UI with @shadcn/lint
(#20731)
* feat(design-system): gate renderer UI with @shadcn/lint
Wires shadcn-ui/lint's Oxlint plugin into the two places this repo already
ratchets: the changed-lines PR gate for rules the renderer can't satisfy
today, and `pnpm lint` for the one that is already at zero.
- config/oxlint-design-system.json: no-restyle (layout allowed),
no-raw-colors, require-static-classes -- scoped to src/renderer/**/*.tsx,
run over added lines only. Measured at 10 findings across the last 60
commits (771 changed files), so it holds the line without a migration.
- config/oxlint-dead-classes.json: no-unknown-classes repo-wide, with the
renderer's plain-CSS hook namespaces allow-listed. Now at zero.
- no-inline-styles and no-arbitrary-values stay off; STYLEGUIDE says why.
Fixes the three live bugs the linter found:
- `--editor-surface` never reached `@theme inline`, so `bg-editor-surface`
generated no CSS -- 12 editor/artifact/notebook panes fell through to the
page background instead of #1e1e1e in dark mode.
- `scrollbar-none` is not a Tailwind utility and was declared nowhere, so
the remote file browser breadcrumbs showed the scrollbar they meant to
hide. Declared as a real `@utility`.
- Notebook markdown cells used `markdown-preview-body`, which no stylesheet
defines; the styled class is `markdown-body`. They rendered unstyled.
* ci: run the dead-class gate in PR CI
`pnpm lint` gained check:dead-classes, and pr-workflow-lint-parity requires
every `pnpm lint` step to have a matching step in pr.yml.
* fix(notebook): keep markdown theme selectors working
---
.github/workflows/pr.yml | 3 +
AGENTS.md | 3 +-
config/oxlint-dead-classes.json | 77 ++
config/oxlint-design-system.json | 54 ++
config/scripts/check-changed-code-quality.mjs | 6 +
package.json | 5 +-
pnpm-lock.yaml | 776 +++++++++++++++++-
pnpm-workspace.yaml | 1 +
src/renderer/src/assets/main.css | 13 +
.../assets/theme-utility-generation.test.ts | 19 +
.../src/components/editor/IpynbCellEditor.tsx | 27 +-
11 files changed, 975 insertions(+), 9 deletions(-)
create mode 100644 config/oxlint-dead-classes.json
create mode 100644 config/oxlint-design-system.json
create mode 100644 src/renderer/src/assets/theme-utility-generation.test.ts
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 2f54f2ae785..7418e8f80aa 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -170,6 +170,9 @@ jobs:
- name: Check reliability gate manifest
run: pnpm run check:reliability-gates
+ - name: Enforce dead design-system classes
+ run: pnpm run check:dead-classes
+
- name: Check VM runtime rollback compatibility
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
diff --git a/AGENTS.md b/AGENTS.md
index 74c049a49fd..0c11d13a9ca 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# Design System
-All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow [`docs/STYLEGUIDE.md`](./docs/STYLEGUIDE.md). Use the tokens defined in `src/renderer/src/assets/main.css` (the canonical source) and the shadcn primitives in `src/renderer/src/components/ui/`. Don't invent new color values, font sizes, or shadow tiers when a documented one already covers the role. When STYLEGUIDE.md is silent, follow the resolution order in its final section.
+All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow [`docs/STYLEGUIDE.md`](./docs/STYLEGUIDE.md). Most of it is linted: `pnpm run check:code-quality:changed` fails on new restyles of a `components/ui/` primitive, raw palette colors, and computed `className` strings; `pnpm lint` fails on any class Tailwind cannot generate. See the Enforcement section of the style guide before suppressing either. Use the tokens defined in `src/renderer/src/assets/main.css` (the canonical source) and the shadcn primitives in `src/renderer/src/components/ui/`. Don't invent new color values, font sizes, or shadow tiers when a documented one already covers the role. When STYLEGUIDE.md is silent, follow the resolution order in its final section.
## Electron UI Validation
@@ -46,6 +46,7 @@ Avoid type assertions except `as const`. Unavoidable casts need a line-specific
- **Typecheck**: `pnpm tc` (or `tc:node` / `tc:cli` / `tc:web`)
- **Test**: `pnpm test [path/to/file.test.ts]`
- **Lint**: `oxlint`, or `pnpm run check:code-quality:changed` for changed files (full `pnpm lint` is slow); format with `pnpm format`
+- **Design system**: `pnpm run lint:design-system` for the full renderer report (not a gate); the changed-lines gate above is what CI enforces
# Considerations
diff --git a/config/oxlint-dead-classes.json b/config/oxlint-dead-classes.json
new file mode 100644
index 00000000000..ad58853134f
--- /dev/null
+++ b/config/oxlint-dead-classes.json
@@ -0,0 +1,77 @@
+{
+ "$schema": "../node_modules/oxlint/configuration_schema.json",
+ "plugins": [],
+ "categories": {
+ "correctness": "off",
+ "suspicious": "off",
+ "pedantic": "off",
+ "perf": "off",
+ "style": "off",
+ "restriction": "off",
+ "nursery": "off"
+ },
+ "jsPlugins": [
+ {
+ "name": "shadcn",
+ "specifier": "@shadcn/lint"
+ }
+ ],
+ "settings": {
+ "shadcn": {
+ "note": "See docs/STYLEGUIDE.md for the role each token and primitive plays."
+ }
+ },
+ "rules": {},
+ "overrides": [
+ {
+ "files": ["**/src/renderer/**/*.tsx"],
+ "rules": {
+ "shadcn/no-unknown-classes": [
+ "error",
+ {
+ "allow": [
+ "agent-map-*",
+ "comment-md-*",
+ "compact-agent-*",
+ "feature-wall-*",
+ "is-*",
+ "markdown-annotation-*",
+ "markdown-body",
+ "markdown-dark",
+ "markdown-doc-link*",
+ "markdown-light",
+ "markdown-preview",
+ "markdown-preview-search*",
+ "markdown-preview-shell",
+ "markdown-review-*",
+ "markdown-toc-*",
+ "mobile-browser-driver-banner",
+ "mobile-driver-banner",
+ "native-chat-*",
+ "orca-*",
+ "pdfViewer",
+ "popover-scroll-content",
+ "popover-wheel-scroll",
+ "ravpr-*",
+ "ravs-*",
+ "scrollbar-editor",
+ "scrollbar-sleek",
+ "scrollbar-sleek-lg",
+ "scrollbar-sleek-parent",
+ "toaster",
+ "worktree-sidebar-scrollbar",
+ "xterm-*"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "files": ["**/*.test.tsx"],
+ "rules": {
+ "shadcn/no-unknown-classes": "off"
+ }
+ }
+ ],
+ "ignorePatterns": ["**/node_modules", "**/dist", "**/out", "cloud/**", "mobile/**"]
+}
diff --git a/config/oxlint-design-system.json b/config/oxlint-design-system.json
new file mode 100644
index 00000000000..23b8df517d3
--- /dev/null
+++ b/config/oxlint-design-system.json
@@ -0,0 +1,54 @@
+{
+ "$schema": "../node_modules/oxlint/configuration_schema.json",
+ "plugins": [],
+ "categories": {
+ "correctness": "off",
+ "suspicious": "off",
+ "pedantic": "off",
+ "perf": "off",
+ "style": "off",
+ "restriction": "off",
+ "nursery": "off"
+ },
+ "jsPlugins": [
+ {
+ "name": "shadcn",
+ "specifier": "@shadcn/lint"
+ }
+ ],
+ "settings": {
+ "shadcn": {
+ "note": "See docs/STYLEGUIDE.md for the role each token and primitive plays."
+ }
+ },
+ "rules": {},
+ "overrides": [
+ {
+ "files": ["**/src/renderer/**/*.tsx"],
+ "rules": {
+ "shadcn/no-restyle": [
+ "error",
+ {
+ "allow": ["layout"]
+ }
+ ],
+ "shadcn/no-raw-colors": [
+ "error",
+ {
+ "allow": ["shadow-floating"]
+ }
+ ],
+ "shadcn/require-static-classes": "error"
+ }
+ },
+ {
+ "files": ["**/*.test.tsx"],
+ "rules": {
+ "shadcn/no-restyle": "off",
+ "shadcn/no-raw-colors": "off",
+ "shadcn/require-static-classes": "off"
+ }
+ }
+ ],
+ "ignorePatterns": ["**/node_modules", "**/dist", "**/out", "cloud/**", "mobile/**"]
+}
diff --git a/config/scripts/check-changed-code-quality.mjs b/config/scripts/check-changed-code-quality.mjs
index a1b5b2fc88a..31b6d24953a 100644
--- a/config/scripts/check-changed-code-quality.mjs
+++ b/config/scripts/check-changed-code-quality.mjs
@@ -29,6 +29,12 @@ export const OXLINT_SCANS = [
{
label: 'React Doctor',
args: ['--config', 'config/oxlint-react-doctor.json']
+ },
+ {
+ // Why changed-lines only: the renderer carries ~4.7k pre-existing restyle/raw-color
+ // findings. Gating added lines holds the line without a repo-wide migration.
+ label: 'design system',
+ args: ['--config', 'config/oxlint-design-system.json']
}
]
diff --git a/package.json b/package.json
index a9f12759f74..174a8718641 100644
--- a/package.json
+++ b/package.json
@@ -14,13 +14,15 @@
"audit:perf": "oxlint --config config/oxlint-performance-audit.json --format json src",
"test:perf:contracts": "vitest run --config config/vitest.performance.config.ts",
"format": "oxfmt --write .",
- "lint": "oxlint && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:readme-local-links && pnpm run verify:rpc-params-catalog && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-runtime-catalog && pnpm run verify:localization-extraction && pnpm run verify:localization-coverage",
+ "lint": "oxlint && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:dead-classes && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:readme-local-links && pnpm run verify:rpc-params-catalog && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-runtime-catalog && pnpm run verify:localization-extraction && 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-native-plugins.json src config tests mobile --deny-warnings",
"audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests --deny-warnings",
"audit:react-doctor": "pnpm dlx react-doctor@0.9.1 . --yes --no-supply-chain --no-telemetry --blocking none",
"audit:dead-code": "pnpm dlx knip@5.88.1 --config config/knip.json",
"check:code-quality:changed": "node config/scripts/check-changed-code-quality.mjs",
+ "check:dead-classes": "oxlint --config config/oxlint-dead-classes.json src/renderer",
+ "lint:design-system": "oxlint --config config/oxlint-design-system.json src/renderer",
"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",
@@ -199,6 +201,7 @@
"@monaco-editor/react": "^4.7.0",
"@playwright/test": "^1.59.1",
"@sanity/diff-match-patch": "^3.2.0",
+ "@shadcn/lint": "^0.1.0",
"@stablyai/playwright-test": "^2.1.14",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/react-virtual": "^3.14.10",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b2f6425aafd..6e9de58b228 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -219,6 +219,9 @@ importers:
'@sanity/diff-match-patch':
specifier: ^3.2.0
version: 3.2.0
+ '@shadcn/lint':
+ specifier: ^0.1.0
+ version: 0.1.0(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)
'@stablyai/playwright-test':
specifier: ^2.1.14
version: 2.1.14(@playwright/test@1.59.1)(zod@4.5.4)
@@ -718,6 +721,12 @@ packages:
'@braintree/sanitize-url@7.1.2':
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
+ '@cacheable/memory@2.2.0':
+ resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==}
+
+ '@cacheable/utils@2.5.0':
+ resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
+
'@chevrotain/types@11.1.2':
resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}
@@ -978,6 +987,40 @@ packages:
cpu: [x64]
os: [win32]
+ '@eslint-community/eslint-utils@4.10.1':
+ resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.23.5':
+ resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/config-helpers@0.7.0':
+ resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@1.2.1':
+ resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/object-schema@3.0.5':
+ resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/plugin-kit@0.7.3':
+ resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
@@ -1004,6 +1047,26 @@ packages:
peerDependencies:
hono: ^4
+ '@humanfs/core@0.19.2':
+ resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.8':
+ resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/types@0.15.0':
+ resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -1164,6 +1227,15 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@keyv/bigmap@1.3.1':
+ resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ keyv: ^5.6.0
+
+ '@keyv/serialize@1.1.1':
+ resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
+
'@linear/sdk@82.1.0':
resolution: {integrity: sha512-Ok7o+LqXaenx6Um58NQqjQoQanDsCgAIe9yNgpVbqRSh5APz3Ds1kZUz2vWmSNTNATFZm1zDQtEktTMga2X7UQ==}
engines: {node: '>=18.x'}
@@ -1338,42 +1410,84 @@ packages:
cpu: [arm]
os: [android]
+ '@oxc-parser/binding-android-arm-eabi@0.148.0':
+ resolution: {integrity: sha512-pHASv9g5pASxb7akHERZNSkrEqPhFaUix98o7d9hbTpolnnFWl7UiRrcMhCsV1+iVO4/cJwKsbKRJTFNs2tdBQ==}
+ 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-android-arm64@0.148.0':
+ resolution: {integrity: sha512-sg/6Ez0KdAygsu0POELux9wN1Po2CP93WY8eNl4DBKIGprsd4QSHBXOb471Pu9i2OCD5sLkISSb2agZEhVn2Zw==}
+ 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-arm64@0.148.0':
+ resolution: {integrity: sha512-yiSJmzGUvCUaJT8X3j40gVcX+ckuHQMuiOtF8DvzTs5+JtB/7XuHFPp4M+vv5u+HlBtDUd4Ks5pyHpWz8mfnkg==}
+ 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-darwin-x64@0.148.0':
+ resolution: {integrity: sha512-6ZeklaamrMy4H2JmhvcJg6iip59tYILtuLaILxyAHT3l5FDxnI5ihVievAft5ZmAbqtlWHErOi1OpJK8gy1wcA==}
+ 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-freebsd-x64@0.148.0':
+ resolution: {integrity: sha512-vFsPx+a/qFECPnz/H8nC6x6MDvnWscLTCo/5muojEF54ERUq1kdgbvnWo95YnkhjF9sTIcG/uDxQBh1gffaufQ==}
+ 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-gnueabihf@0.148.0':
+ resolution: {integrity: sha512-eOr3M+6iGbbxNL4PSS0VtsyQ2eOUxSBh00BqO22SbolDimPSYsBuLr/LCrZBkiqW2BoabhR6V4R8jrRAay7hjg==}
+ 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-arm-musleabihf@0.148.0':
+ resolution: {integrity: sha512-58ZKDw0mQRbCNfrd2IDyV4o8T7enzGERJn41BH2tjrZVGyiKiFzcfDicuB7Zcpb/1xIOrObovr8Dja6lZi8dLw==}
+ 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}
@@ -1381,6 +1495,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@oxc-parser/binding-linux-arm64-gnu@0.148.0':
+ resolution: {integrity: sha512-Fnu95O4eZ5i++GPvIzBEZ8y4ddTLR+D9paYa8JRaRk6ZK7nHQiWP5xtrhcPQsXqgat1d7sU/d5rbbI0p1FTHSQ==}
+ 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}
@@ -1388,6 +1509,13 @@ packages:
os: [linux]
libc: [musl]
+ '@oxc-parser/binding-linux-arm64-musl@0.148.0':
+ resolution: {integrity: sha512-3CQy/BMdx7N7H3qrcPxUL+a2CwUZodUcf6oq8iJuNZ9C6Ol1aq3mcWzsgySJ7CHFLvpX21ZDPp1r1X0QLbu/AQ==}
+ 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}
@@ -1395,6 +1523,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@oxc-parser/binding-linux-ppc64-gnu@0.148.0':
+ resolution: {integrity: sha512-9LkaYvfiF8hMOw900csAvkf1oxE8XlmMeGowu5BcastSSwV8mKvKRMNU7HsV+ycyj1dQD8pX5qgOw8ja6SJacg==}
+ 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}
@@ -1402,6 +1537,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@oxc-parser/binding-linux-riscv64-gnu@0.148.0':
+ resolution: {integrity: sha512-2GBiM9h26dR4WJfhoMvnFMnFLf7m/kYs4UMqjvrOfQG4BV1nuTJDH22Zc2MQr3INZF7nSKYQ6xlhD3hQ7A6gug==}
+ 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}
@@ -1409,6 +1551,13 @@ packages:
os: [linux]
libc: [musl]
+ '@oxc-parser/binding-linux-riscv64-musl@0.148.0':
+ resolution: {integrity: sha512-uPqZexvKJmEgq4mAu36qe2xTfXZE7oyik1R7KtZ5tl8qKlq1U1fIqTFRUEBZqRGvforoTrGIpatRzcoPKO66RA==}
+ 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}
@@ -1416,6 +1565,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@oxc-parser/binding-linux-s390x-gnu@0.148.0':
+ resolution: {integrity: sha512-9oUHvnTbp7ZraFsTC8PN6XhdhPSSxZumYvixWl7Smi353gEULvK6yV0sXNVrdFMHQeaDKFCi8TgDhNK7/A+Y+Q==}
+ 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}
@@ -1423,6 +1579,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@oxc-parser/binding-linux-x64-gnu@0.148.0':
+ resolution: {integrity: sha512-2qhDSJwKzbSZzF7lDqqk8sr/yXsmwr3PeUa4/nazIF+zFAYz1gVPEfC34GQtGxzJUUmklaYAL63368LEfrMeyw==}
+ 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}
@@ -1430,12 +1593,25 @@ packages:
os: [linux]
libc: [musl]
+ '@oxc-parser/binding-linux-x64-musl@0.148.0':
+ resolution: {integrity: sha512-qQoPDZUFV0bh9xA09XydmkjMBpgc1ukJuhMvzQ9QeVmFaHTS9W5TE5CoLmSl3QQyUP9OuHO3x/WPZTIIZPWR3Q==}
+ 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-openharmony-arm64@0.148.0':
+ resolution: {integrity: sha512-1UGbaQWEXUCLqAmaR5kwRDjx/R4S5LQKZkM9CHmaHkuKhriOF32aRLfS0jCRNE2yGQJLMEA1z9UucbBVqjXnDw==}
+ 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}
@@ -1447,18 +1623,36 @@ packages:
cpu: [arm64]
os: [win32]
+ '@oxc-parser/binding-win32-arm64-msvc@0.148.0':
+ resolution: {integrity: sha512-pWKdzRDNG2+NK4h/V6U/CYERcfYD6u28h5IB/VJVsrZaD3muvE58tUj22lieL5vLZ+XFi1GPv9YXckZbJZ9BLA==}
+ 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-ia32-msvc@0.148.0':
+ resolution: {integrity: sha512-i3p4x+mvwtjcE1J5HM6V7ggsbXiznExN/4MkNyOy3dfXrVV3bnkSfmZxvo6/84qCVX4ShkpNE1SKt9biIF31GQ==}
+ 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-parser/binding-win32-x64-msvc@0.148.0':
+ resolution: {integrity: sha512-Ye6vB7VQulghWYkYkECOBYFRVEizz4XyRTUAv+t8BuyurhKU7uD0P9eowL+mKG5Mf8MSYx+DI3Cm8SKZvYG7bQ==}
+ 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}
@@ -1469,6 +1663,9 @@ packages:
'@oxc-project/types@0.141.0':
resolution: {integrity: sha512-S4as7z0j0xQkXcJlyY5ehntwK8/wRkQb9Cyqw+J/N2rkWGQGK0SxD6X6DhQTc7qsxVTBxXbxZtBJh3mr3PtIzQ==}
+ '@oxc-project/types@0.148.0':
+ resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==}
+
'@oxfmt/binding-android-arm-eabi@0.65.0':
resolution: {integrity: sha512-M10Gs1SSpTNI6ahGx3M/OlIdUF4hkaP6OgUb+MS79t/Pgflk3r1nW5gPFqsZGUAXg0H1AfANT9AvLdBSTIhZKg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2644,6 +2841,15 @@ packages:
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
+ '@shadcn/lint@0.1.0':
+ resolution: {integrity: sha512-UDSxO4eQa8UAclN1tChum+L336CL2uB2ZLGYiJ7r/GDrYUBOKPWWNUVoAfh2dZs4LhcwJRCn62+fKG12eAy1FQ==}
+ engines: {node: '>=20.19'}
+ peerDependencies:
+ eslint: '>=9.30.0'
+ peerDependenciesMeta:
+ eslint:
+ optional: true
+
'@sindresorhus/is@4.6.0':
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
@@ -3282,6 +3488,9 @@ packages:
'@types/http-cache-semantics@4.2.0':
resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==}
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
'@types/katex@0.16.8':
resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
@@ -3353,10 +3562,47 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+ '@typescript-eslint/parser@8.70.0':
+ resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/project-service@8.70.0':
+ resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/scope-manager@8.70.0':
+ resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.70.0':
+ resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
'@typescript-eslint/types@8.60.0':
resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/types@8.70.0':
+ resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.70.0':
+ resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/visitor-keys@8.70.0':
+ resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript/typescript-aix-ppc64@7.0.2':
resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==}
engines: {node: '>=16.20.0'}
@@ -3575,6 +3821,11 @@ packages:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
@@ -3596,6 +3847,9 @@ packages:
ajv:
optional: true
+ ajv@6.15.0:
+ resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
+
ajv@8.20.0:
resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
@@ -3771,6 +4025,9 @@ packages:
resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
engines: {node: '>=8'}
+ cacheable@2.5.0:
+ resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==}
+
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -3882,6 +4139,11 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
+ cn@0.2.6:
+ resolution: {integrity: sha512-+i4L0zUGgRcEnhsxueVrP7iBGxBx5iD0WOTYg1MwFEu2ZyCmH5Ov2V1cul2Ht5UchRQQgftCf4be/RxspuW6QQ==}
+ engines: {node: '>=20'}
+ hasBin: true
+
code-block-writer@13.0.3:
resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
@@ -4194,6 +4456,9 @@ packages:
babel-plugin-macros:
optional: true
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@@ -4447,15 +4712,37 @@ packages:
resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
eslint-visitor-keys@5.0.1:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ eslint@10.10.0:
+ resolution: {integrity: sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@11.2.0:
+ resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
@@ -4470,6 +4757,10 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
@@ -4524,6 +4815,12 @@ packages:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
fast-sha256@1.3.0:
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
@@ -4561,6 +4858,9 @@ packages:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
+ file-entry-cache@11.1.5:
+ resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==}
+
filelist@1.0.6:
resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==}
@@ -4576,9 +4876,19 @@ packages:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
flairup@1.0.0:
resolution: {integrity: sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==}
+ flat-cache@6.1.23:
+ resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==}
+
+ flatted@3.4.4:
+ resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
+
form-data@4.0.6:
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
engines: {node: '>= 6'}
@@ -4685,6 +4995,10 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
glob@13.0.6:
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
engines: {node: 18 || 20 || >=22}
@@ -4738,6 +5052,10 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
+ hashery@1.5.1:
+ resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==}
+ engines: {node: '>=20'}
+
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
@@ -4798,6 +5116,12 @@ packages:
resolution: {integrity: sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==}
engines: {node: '>=16.9.0'}
+ hookified@1.15.1:
+ resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
+
+ hookified@2.2.0:
+ resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==}
+
hosted-git-info@4.1.0:
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
engines: {node: '>=10'}
@@ -4883,6 +5207,10 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
indent-string@4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
@@ -5077,12 +5405,18 @@ packages:
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
engines: {node: '>=16'}
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
@@ -5111,6 +5445,9 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ keyv@5.6.0:
+ resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
+
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
@@ -5131,6 +5468,10 @@ packages:
lazy-val@1.0.5:
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -5224,6 +5565,10 @@ packages:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
lodash-es@4.18.1:
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
@@ -5576,6 +5921,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
@@ -5672,6 +6020,10 @@ packages:
resolution: {integrity: sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==}
hasBin: true
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
ora@8.2.0:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
@@ -5690,6 +6042,10 @@ packages:
resolution: {integrity: sha512-uFkGGr1KMWd6aWv9UAqooYrN78trw8MWWmoPvgWokfBEUq1+eiIQ+qfj3wokhy0fxtZWZk+0dHoS7/yRTJtd6w==}
engines: {node: ^20.19.0 || >=22.12.0}
+ oxc-parser@0.148.0:
+ resolution: {integrity: sha512-syxUKHeUll89RIABQADcI7sikYrwyssvA6gj4phSSIPezKVM8yMaLAiLLSc7fmzVvrwybfFGFbW5zme9sX87rg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
oxfmt@0.65.0:
resolution: {integrity: sha512-SgS5VgnP42T0zl3zWD+xoH8FCqg1SAFnSRoOT/qeoa6gxcYIqrDMOmcXIg/EWSN92Du4ogB4riuKhKd6Y4CGhw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5740,6 +6096,10 @@ packages:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
p-retry@4.6.2:
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
engines: {node: '>=8'}
@@ -5892,6 +6252,10 @@ packages:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -5971,6 +6335,10 @@ packages:
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
@@ -5978,6 +6346,10 @@ packages:
resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==}
engines: {node: '>=16.0.0'}
+ qified@0.10.1:
+ resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
+ engines: {node: '>=20'}
+
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
@@ -6650,6 +7022,12 @@ packages:
ts-algebra@2.0.0:
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
ts-dedent@2.2.0:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
@@ -6673,6 +7051,10 @@ packages:
tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
type-fest@0.13.1:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
engines: {node: '>=10'}
@@ -6767,6 +7149,9 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
use-callback-ref@1.3.3:
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
@@ -6907,6 +7292,10 @@ packages:
engines: {node: '>=8'}
hasBin: true
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -7278,6 +7667,18 @@ snapshots:
'@braintree/sanitize-url@7.1.2': {}
+ '@cacheable/memory@2.2.0':
+ dependencies:
+ '@cacheable/utils': 2.5.0
+ '@keyv/bigmap': 1.3.1(keyv@5.6.0)
+ hookified: 1.15.1
+ keyv: 5.6.0
+
+ '@cacheable/utils@2.5.0':
+ dependencies:
+ hashery: 1.5.1
+ keyv: 5.6.0
+
'@chevrotain/types@11.1.2': {}
'@croct/json5-parser@0.2.2':
@@ -7524,6 +7925,40 @@ snapshots:
'@esbuild/win32-x64@0.25.12':
optional: true
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))':
+ dependencies:
+ eslint: 10.10.0(jiti@2.7.0)(supports-color@7.2.0)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.23.5(supports-color@7.2.0)':
+ dependencies:
+ '@eslint/object-schema': 3.0.5
+ debug: 4.4.3(supports-color@7.2.0)
+ minimatch: 10.2.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.7.0':
+ dependencies:
+ '@eslint/core': 1.2.1
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/core@1.2.1':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/object-schema@3.0.5': {}
+
+ '@eslint/plugin-kit@0.7.3':
+ dependencies:
+ '@eslint/core': 1.2.1
+ levn: 0.4.1
+
'@floating-ui/core@1.7.5':
dependencies:
'@floating-ui/utils': 0.2.11
@@ -7549,6 +7984,22 @@ snapshots:
dependencies:
hono: 4.13.0
+ '@humanfs/core@0.19.2':
+ dependencies:
+ '@humanfs/types': 0.15.0
+
+ '@humanfs/node@0.16.8':
+ dependencies:
+ '@humanfs/core': 0.19.2
+ '@humanfs/types': 0.15.0
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanfs/types@0.15.0': {}
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
'@iconify/types@2.0.0': {}
'@iconify/utils@3.1.1':
@@ -7699,6 +8150,14 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
+ '@keyv/bigmap@1.3.1(keyv@5.6.0)':
+ dependencies:
+ hashery: 1.5.1
+ hookified: 1.15.1
+ keyv: 5.6.0
+
+ '@keyv/serialize@1.1.1': {}
+
'@linear/sdk@82.1.0(graphql@16.14.2)':
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2)
@@ -7887,51 +8346,99 @@ snapshots:
'@oxc-parser/binding-android-arm-eabi@0.141.0':
optional: true
+ '@oxc-parser/binding-android-arm-eabi@0.148.0':
+ optional: true
+
'@oxc-parser/binding-android-arm64@0.141.0':
optional: true
+ '@oxc-parser/binding-android-arm64@0.148.0':
+ optional: true
+
'@oxc-parser/binding-darwin-arm64@0.141.0':
optional: true
+ '@oxc-parser/binding-darwin-arm64@0.148.0':
+ optional: true
+
'@oxc-parser/binding-darwin-x64@0.141.0':
optional: true
+ '@oxc-parser/binding-darwin-x64@0.148.0':
+ optional: true
+
'@oxc-parser/binding-freebsd-x64@0.141.0':
optional: true
+ '@oxc-parser/binding-freebsd-x64@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-arm-gnueabihf@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-arm-musleabihf@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-arm-musleabihf@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-arm64-gnu@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-arm64-gnu@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-arm64-musl@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-arm64-musl@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-ppc64-gnu@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-ppc64-gnu@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-riscv64-gnu@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-riscv64-gnu@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-riscv64-musl@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-riscv64-musl@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-s390x-gnu@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-s390x-gnu@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-x64-gnu@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-x64-gnu@0.148.0':
+ optional: true
+
'@oxc-parser/binding-linux-x64-musl@0.141.0':
optional: true
+ '@oxc-parser/binding-linux-x64-musl@0.148.0':
+ optional: true
+
'@oxc-parser/binding-openharmony-arm64@0.141.0':
optional: true
+ '@oxc-parser/binding-openharmony-arm64@0.148.0':
+ optional: true
+
'@oxc-parser/binding-wasm32-wasi@0.141.0':
dependencies:
'@emnapi/core': 1.11.2
@@ -7942,18 +8449,30 @@ snapshots:
'@oxc-parser/binding-win32-arm64-msvc@0.141.0':
optional: true
+ '@oxc-parser/binding-win32-arm64-msvc@0.148.0':
+ optional: true
+
'@oxc-parser/binding-win32-ia32-msvc@0.141.0':
optional: true
+ '@oxc-parser/binding-win32-ia32-msvc@0.148.0':
+ optional: true
+
'@oxc-parser/binding-win32-x64-msvc@0.141.0':
optional: true
+ '@oxc-parser/binding-win32-x64-msvc@0.148.0':
+ optional: true
+
'@oxc-project/runtime@0.101.0': {}
'@oxc-project/types@0.101.0': {}
'@oxc-project/types@0.141.0': {}
+ '@oxc-project/types@0.148.0':
+ optional: true
+
'@oxfmt/binding-android-arm-eabi@0.65.0':
optional: true
@@ -8988,6 +9507,18 @@ snapshots:
'@sec-ant/readable-stream@0.4.1': {}
+ '@shadcn/lint@0.1.0(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)':
+ dependencies:
+ '@eslint/core': 0.17.0
+ '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)
+ cn: 0.2.6
+ optionalDependencies:
+ eslint: 10.10.0(jiti@2.7.0)(supports-color@7.2.0)
+ oxc-parser: 0.148.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
'@sindresorhus/is@4.6.0': {}
'@sindresorhus/merge-streams@4.0.0': {}
@@ -9621,6 +10152,8 @@ snapshots:
'@types/http-cache-semantics@4.2.0': {}
+ '@types/json-schema@7.0.15': {}
+
'@types/katex@0.16.8': {}
'@types/keyv@3.1.4':
@@ -9696,8 +10229,60 @@ snapshots:
dependencies:
'@types/node': 25.9.5
+ '@typescript-eslint/parser@8.70.0(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.70.0
+ '@typescript-eslint/types': 8.70.0
+ '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@7.0.2)
+ '@typescript-eslint/visitor-keys': 8.70.0
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 10.10.0(jiti@2.7.0)(supports-color@7.2.0)
+ typescript: 7.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.70.0(supports-color@7.2.0)(typescript@7.0.2)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@7.0.2)
+ '@typescript-eslint/types': 8.70.0
+ debug: 4.4.3(supports-color@7.2.0)
+ typescript: 7.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.70.0':
+ dependencies:
+ '@typescript-eslint/types': 8.70.0
+ '@typescript-eslint/visitor-keys': 8.70.0
+
+ '@typescript-eslint/tsconfig-utils@8.70.0(typescript@7.0.2)':
+ dependencies:
+ typescript: 7.0.2
+
'@typescript-eslint/types@8.60.0': {}
+ '@typescript-eslint/types@8.70.0': {}
+
+ '@typescript-eslint/typescript-estree@8.70.0(supports-color@7.2.0)(typescript@7.0.2)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.70.0(supports-color@7.2.0)(typescript@7.0.2)
+ '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@7.0.2)
+ '@typescript-eslint/types': 8.70.0
+ '@typescript-eslint/visitor-keys': 8.70.0
+ debug: 4.4.3(supports-color@7.2.0)
+ minimatch: 10.2.5
+ semver: 7.8.1
+ tinyglobby: 0.2.16
+ ts-api-utils: 2.5.0(typescript@7.0.2)
+ typescript: 7.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.70.0':
+ dependencies:
+ '@typescript-eslint/types': 8.70.0
+ eslint-visitor-keys: 5.0.1
+
'@typescript/typescript-aix-ppc64@7.0.2':
optional: true
@@ -9867,6 +10452,10 @@ snapshots:
mime-types: 3.0.2
negotiator: 1.0.0
+ acorn-jsx@5.3.2(acorn@8.16.0):
+ dependencies:
+ acorn: 8.16.0
+
acorn@8.16.0: {}
agent-base@7.1.4: {}
@@ -9877,6 +10466,13 @@ snapshots:
optionalDependencies:
ajv: 8.20.0
+ ajv@6.15.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -10103,6 +10699,14 @@ snapshots:
normalize-url: 6.1.0
responselike: 2.0.1
+ cacheable@2.5.0:
+ dependencies:
+ '@cacheable/memory': 2.2.0
+ '@cacheable/utils': 2.5.0
+ hookified: 1.15.1
+ keyv: 5.6.0
+ qified: 0.10.1
+
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -10201,6 +10805,8 @@ snapshots:
- '@types/react'
- '@types/react-dom'
+ cn@0.2.6: {}
+
code-block-writer@13.0.3: {}
color-convert@2.0.1:
@@ -10500,6 +11106,8 @@ snapshots:
dedent@1.7.2: {}
+ deep-is@0.1.4: {}
+
deepmerge@4.3.1: {}
default-browser-id@5.0.1: {}
@@ -10791,8 +11399,7 @@ snapshots:
escape-html@1.0.3: {}
- escape-string-regexp@4.0.0:
- optional: true
+ escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {}
@@ -10803,10 +11410,59 @@ snapshots:
esrecurse: 4.3.0
estraverse: 5.3.0
+ eslint-visitor-keys@3.4.3: {}
+
eslint-visitor-keys@5.0.1: {}
+ eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(jiti@2.7.0)(supports-color@7.2.0))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.23.5(supports-color@7.2.0)
+ '@eslint/config-helpers': 0.7.0
+ '@eslint/core': 1.2.1
+ '@eslint/plugin-kit': 0.7.3
+ '@humanfs/node': 0.16.8
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.15.0
+ cross-spawn: 7.0.6
+ debug: 4.4.3(supports-color@7.2.0)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 9.1.2
+ eslint-visitor-keys: 5.0.1
+ espree: 11.2.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 11.1.5
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ minimatch: 10.2.5
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@11.2.0:
+ dependencies:
+ acorn: 8.16.0
+ acorn-jsx: 5.3.2(acorn@8.16.0)
+ eslint-visitor-keys: 5.0.1
+
esprima@4.0.1: {}
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
@@ -10819,6 +11475,8 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
+ esutils@2.0.3: {}
+
etag@1.8.1: {}
eventemitter3@5.0.4: {}
@@ -10912,6 +11570,10 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.8
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
fast-sha256@1.3.0: {}
fast-string-truncated-width@3.0.3: {}
@@ -10946,6 +11608,10 @@ snapshots:
dependencies:
is-unicode-supported: 2.1.0
+ file-entry-cache@11.1.5:
+ dependencies:
+ flat-cache: 6.1.23
+
filelist@1.0.6:
dependencies:
minimatch: 5.1.9
@@ -10970,8 +11636,21 @@ snapshots:
locate-path: 5.0.0
path-exists: 4.0.0
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
flairup@1.0.0: {}
+ flat-cache@6.1.23:
+ dependencies:
+ cacheable: 2.5.0
+ flatted: 3.4.4
+ hookified: 1.15.1
+
+ flatted@3.4.4: {}
+
form-data@4.0.6:
dependencies:
asynckit: 0.4.0
@@ -11080,6 +11759,10 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
glob@13.0.6:
dependencies:
minimatch: 10.2.5
@@ -11159,6 +11842,10 @@ snapshots:
dependencies:
has-symbols: 1.1.0
+ hashery@1.5.1:
+ dependencies:
+ hookified: 1.15.1
+
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@@ -11293,6 +11980,10 @@ snapshots:
hono@4.13.0: {}
+ hookified@1.15.1: {}
+
+ hookified@2.2.0: {}
+
hosted-git-info@4.1.0:
dependencies:
lru-cache: 6.0.0
@@ -11396,6 +12087,8 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
+ imurmurhash@0.1.4: {}
+
indent-string@4.0.0: {}
inflight@1.0.6:
@@ -11531,10 +12224,14 @@ snapshots:
'@babel/runtime': 7.29.7
ts-algebra: 2.0.0
+ json-schema-traverse@0.4.1: {}
+
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
json-stringify-safe@5.0.1:
optional: true
@@ -11564,6 +12261,10 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ keyv@5.6.0:
+ dependencies:
+ '@keyv/serialize': 1.1.1
+
khroma@2.1.0: {}
kleur@3.0.3: {}
@@ -11576,6 +12277,11 @@ snapshots:
lazy-val@1.0.5: {}
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
lightningcss-android-arm64@1.32.0:
optional: true
@@ -11651,6 +12357,10 @@ snapshots:
dependencies:
p-locate: 4.1.0
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
lodash-es@4.18.1: {}
lodash.escaperegexp@4.1.2: {}
@@ -12248,6 +12958,8 @@ snapshots:
nanoid@3.3.18: {}
+ natural-compare@1.4.0: {}
+
negotiator@1.0.0: {}
node-abi@4.33.0:
@@ -12339,6 +13051,15 @@ snapshots:
opentype.js@2.0.0: {}
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
ora@8.2.0:
dependencies:
chalk: 5.6.2
@@ -12392,6 +13113,31 @@ snapshots:
'@oxc-parser/binding-win32-ia32-msvc': 0.141.0
'@oxc-parser/binding-win32-x64-msvc': 0.141.0
+ oxc-parser@0.148.0:
+ dependencies:
+ '@oxc-project/types': 0.148.0
+ optionalDependencies:
+ '@oxc-parser/binding-android-arm-eabi': 0.148.0
+ '@oxc-parser/binding-android-arm64': 0.148.0
+ '@oxc-parser/binding-darwin-arm64': 0.148.0
+ '@oxc-parser/binding-darwin-x64': 0.148.0
+ '@oxc-parser/binding-freebsd-x64': 0.148.0
+ '@oxc-parser/binding-linux-arm-gnueabihf': 0.148.0
+ '@oxc-parser/binding-linux-arm-musleabihf': 0.148.0
+ '@oxc-parser/binding-linux-arm64-gnu': 0.148.0
+ '@oxc-parser/binding-linux-arm64-musl': 0.148.0
+ '@oxc-parser/binding-linux-ppc64-gnu': 0.148.0
+ '@oxc-parser/binding-linux-riscv64-gnu': 0.148.0
+ '@oxc-parser/binding-linux-riscv64-musl': 0.148.0
+ '@oxc-parser/binding-linux-s390x-gnu': 0.148.0
+ '@oxc-parser/binding-linux-x64-gnu': 0.148.0
+ '@oxc-parser/binding-linux-x64-musl': 0.148.0
+ '@oxc-parser/binding-openharmony-arm64': 0.148.0
+ '@oxc-parser/binding-win32-arm64-msvc': 0.148.0
+ '@oxc-parser/binding-win32-ia32-msvc': 0.148.0
+ '@oxc-parser/binding-win32-x64-msvc': 0.148.0
+ optional: true
+
oxfmt@0.65.0:
dependencies:
tinypool: 2.1.0
@@ -12469,6 +13215,10 @@ snapshots:
dependencies:
p-limit: 2.3.0
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
p-retry@4.6.2:
dependencies:
'@types/retry': 0.12.0
@@ -12607,6 +13357,8 @@ snapshots:
powershell-utils@0.1.0: {}
+ prelude-ls@1.2.1: {}
+
pretty-format@27.5.1:
dependencies:
ansi-regex: 5.0.1
@@ -12725,12 +13477,18 @@ snapshots:
end-of-stream: 1.4.5
once: 1.4.0
+ punycode@2.3.1: {}
+
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.5: {}
+ qified@0.10.1:
+ dependencies:
+ hookified: 2.2.0
+
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
@@ -13528,6 +14286,10 @@ snapshots:
ts-algebra@2.0.0: {}
+ ts-api-utils@2.5.0(typescript@7.0.2):
+ dependencies:
+ typescript: 7.0.2
+
ts-dedent@2.2.0: {}
ts-morph@26.0.0:
@@ -13549,6 +14311,10 @@ snapshots:
tweetnacl@1.0.3: {}
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
type-fest@0.13.1:
optional: true
@@ -13668,6 +14434,10 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.8):
dependencies:
react: 19.2.8
@@ -13782,6 +14552,8 @@ snapshots:
siginfo: 2.0.0
stackback: 0.0.2
+ word-wrap@1.2.5: {}
+
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 9c0ee568c74..68ae103f6ec 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -17,6 +17,7 @@ minimumReleaseAgeExclude:
- pdfjs-dist@6.3.289
- zod@4.5.4
- electron@43.7.0
+ - '@shadcn/lint@0.1.0'
shamefullyHoist: true
# Orca always launches the user's own resolved Claude CLI via
diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css
index 8793b1f17ec..693e7572ddf 100644
--- a/src/renderer/src/assets/main.css
+++ b/src/renderer/src/assets/main.css
@@ -60,6 +60,7 @@
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
+ --color-editor-surface: var(--editor-surface);
--color-agent-question: var(--agent-question);
--color-agent-question-text: var(--agent-question-text);
--color-chart-1: var(--chart-1);
@@ -509,6 +510,18 @@
}
}
+/* Why @utility, not a plain class: this is a Tailwind-shaped name, so it has to be
+ one Tailwind generates or `scrollbar-none` silently produces no CSS. */
+@utility scrollbar-none {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+
+ &::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ }
+}
+
/* ── Sleek scrollbar (VS Code-like) ─────────────────── */
.scrollbar-sleek {
diff --git a/src/renderer/src/assets/theme-utility-generation.test.ts b/src/renderer/src/assets/theme-utility-generation.test.ts
new file mode 100644
index 00000000000..d17d8a10b4e
--- /dev/null
+++ b/src/renderer/src/assets/theme-utility-generation.test.ts
@@ -0,0 +1,19 @@
+import fs from 'node:fs'
+import { describe, expect, it } from 'vitest'
+
+const mainCss = fs.readFileSync(new URL('./main.css', import.meta.url), 'utf8')
+const themeBlock = /@theme inline\s*{([\s\S]*?)\n}/.exec(mainCss)?.[1] ?? ''
+
+// Why: a token that never reaches `@theme inline`, and a Tailwind-shaped name that is only a
+// plain CSS selector, both generate no CSS at all -- the utility silently does nothing.
+describe('main.css utility generation', () => {
+ it('exposes --editor-surface to Tailwind so bg-editor-surface generates', () => {
+ expect(mainCss).toMatch(/--editor-surface:/)
+ expect(themeBlock).toMatch(/--color-editor-surface:\s*var\(--editor-surface\)/)
+ })
+
+ it('declares scrollbar-none as a utility rather than a plain class', () => {
+ expect(mainCss).toMatch(/@utility scrollbar-none\s*{/)
+ expect(mainCss).not.toMatch(/^\.scrollbar-none\b/m)
+ })
+})
diff --git a/src/renderer/src/components/editor/IpynbCellEditor.tsx b/src/renderer/src/components/editor/IpynbCellEditor.tsx
index 77f319f034c..3ec18d46701 100644
--- a/src/renderer/src/components/editor/IpynbCellEditor.tsx
+++ b/src/renderer/src/components/editor/IpynbCellEditor.tsx
@@ -1,9 +1,10 @@
-import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
+import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import Editor, { type OnMount } from '@monaco-editor/react'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'
import remarkGfm from 'remark-gfm'
+import { cn } from '@/lib/utils'
import { monaco } from '@/lib/monaco-setup'
import { computeEditorFontSize, resolveEditorFontFamily } from '@/lib/editor-font-zoom'
import { resolveDocumentTheme } from '@/lib/document-theme'
@@ -14,11 +15,27 @@ import type { IpynbCell } from './ipynb-parse'
import MonacoCodeExcerpt from './MonacoCodeExcerpt'
export function IpynbMarkdownCell({ source }: { source: string }): React.JSX.Element {
+ const settings = useAppStore((s) => s.settings)
+ const theme = settings?.theme ?? 'system'
+ const [systemDark, setSystemDark] = useState(() => resolveDocumentTheme('system'))
+ useEffect(() => {
+ if (theme !== 'system' || typeof window.matchMedia !== 'function') {
+ return
+ }
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
+ const onChange = () => setSystemDark(media.matches)
+ onChange()
+ media.addEventListener('change', onChange)
+ return () => media.removeEventListener('change', onChange)
+ }, [theme])
+ const isDark = theme === 'system' ? systemDark : resolveDocumentTheme(theme)
return (
-
-
- {source || '\u00a0'}
-
+
+
+
+ {source || '\u00a0'}
+
+
)
}
From 6f4e4bfa223dfec5804441c81c66d292c9b247f7 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 15 Sep 2026 01:00:51 +0000
Subject: [PATCH 05/58] Update README downloads badge
---
docs/assets/readme-downloads.svg | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg
index bd7a18f8488..a4191395e41 100644
--- a/docs/assets/readme-downloads.svg
+++ b/docs/assets/readme-downloads.svg
@@ -1,5 +1,5 @@
-
+
{showJump ? (