From efbf651c7bb2eec778daf1844f8228e70809ec9f Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 02:46:02 -0700 Subject: [PATCH 01/25] Reduce CI setup costs and fixture failures (#24537) * Let scheduled CI warmers wait and measure WebRTC startup * Measure a smaller daemon shutdown fixture image * Counterbalance WebRTC startup and verify retained fixture files * Record CI fixture measurements and remove temporary pilots * Clarify fixture build dependency cleanup evidence * Make coalesced snapshot fixture delivery deterministic * test: type the PTY write delay observer --- .github/workflows/ci-cache-warmup.yml | 3 +- .../daemon-shutdown-descendants/Dockerfile | 22 +++- .../scripts/ci-cache-warmup-workflow.test.mjs | 9 +- docs/reference/ci-runner-efficiency.md | 124 ++++++++++++++++++ ...owser-route-webrtc-egress.electron.test.ts | 2 + .../ipc/remote-workspace-stale-resync.test.ts | 4 + 6 files changed, 158 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cache-warmup.yml b/.github/workflows/ci-cache-warmup.yml index bdd3e185638..84ed72cc68b 100644 --- a/.github/workflows/ci-cache-warmup.yml +++ b/.github/workflows/ci-cache-warmup.yml @@ -51,7 +51,8 @@ permissions: concurrency: group: ci-cache-warmup-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + # Hourly retries must let an active warmer finish publishing its caches. + cancel-in-progress: ${{ github.event_name != 'schedule' }} jobs: warm: diff --git a/config/docker/daemon-shutdown-descendants/Dockerfile b/config/docker/daemon-shutdown-descendants/Dockerfile index b012d5738e3..298678c6bc6 100644 --- a/config/docker/daemon-shutdown-descendants/Dockerfile +++ b/config/docker/daemon-shutdown-descendants/Dockerfile @@ -3,10 +3,26 @@ FROM node:22-bookworm-slim ENV DEBIAN_FRONTEND=noninteractive ENV NODE_PATH=/usr/local/lib/node_modules -RUN apt-get update \ +RUN set -eu \ + && savedAptMark="$(apt-mark showmanual)" \ + && apt-get update \ && apt-get install -y --no-install-recommends build-essential python3 procps util-linux \ - && rm -rf /var/lib/apt/lists/* \ - && npm install --global --no-fund --no-audit node-pty@1.1.0 + && npm install --global --no-fund --no-audit node-pty@1.1.0 \ + && ptyModule="$(node -e "require('node-pty'); const addon = Object.keys(require.cache).find((path) => path.endsWith('/pty.node')); if (!addon) process.exit(1); process.stdout.write(addon)")" \ + && ldd /usr/local/bin/node "$ptyModule" > /tmp/daemon-runtime-libraries \ + && ! grep -q 'not found' /tmp/daemon-runtime-libraries \ + && apt-mark auto '.*' > /dev/null \ + && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; } \ + && apt-mark manual procps util-linux > /dev/null \ + && awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) next; gsub("^/(usr/)?", "", so); print so }' /tmp/daemon-runtime-libraries \ + | sort -u > /tmp/daemon-runtime-library-paths \ + && xargs -r dpkg-query --search < /tmp/daemon-runtime-library-paths > /tmp/daemon-runtime-packages \ + && cut -d: -f1 /tmp/daemon-runtime-packages | sort -u | xargs -r apt-mark manual \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && ldd /usr/local/bin/node "$ptyModule" > /tmp/daemon-runtime-libraries \ + && ! grep -q 'not found' /tmp/daemon-runtime-libraries \ + && ORCA_BACKGROUND_LAUNCH=1 node -e "const pty = require('node-pty'); const terminal = pty.spawn('/bin/sh', ['-c', 'exit 0'], { cwd: '/tmp', env: process.env }); const timer = setTimeout(() => process.exit(1), 5000); terminal.onExit(({ exitCode }) => { clearTimeout(timer); process.exit(exitCode); });" \ + && rm -rf /root/.npm /root/.cache/node-gyp /var/lib/apt/lists/* /tmp/daemon-runtime-libraries /tmp/daemon-runtime-library-paths /tmp/daemon-runtime-packages COPY run-case.sh /usr/local/bin/run-daemon-shutdown-descendants COPY fixture.cjs /opt/daemon-shutdown-descendants/fixture.cjs diff --git a/config/scripts/ci-cache-warmup-workflow.test.mjs b/config/scripts/ci-cache-warmup-workflow.test.mjs index 07c207bddce..74b15e9b267 100644 --- a/config/scripts/ci-cache-warmup-workflow.test.mjs +++ b/config/scripts/ci-cache-warmup-workflow.test.mjs @@ -74,11 +74,16 @@ it('bounds warming to the required platforms and validates changes without grant expect(workflow.on.push.paths).toContain('.github/actions/prepare-native-runtime/**') expect(workflow.on.schedule).toEqual([{ cron: '41 * * * *' }]) expect(workflow.on.pull_request.paths).toContain('.github/workflows/ci-cache-warmup.yml') - expect(workflow.concurrency['cancel-in-progress']).toBe(true) - expect(workflow.concurrency.group).toContain('github.event.pull_request.number || github.ref') expect(steps[0].with['persist-credentials']).toBe(false) }) +it('lets hourly warmers wait while pushes, PR updates, and manual runs can replace active work', () => { + expect(workflow.concurrency).toEqual({ + group: 'ci-cache-warmup-${{ github.event.pull_request.number || github.ref }}', + 'cancel-in-progress': "${{ github.event_name != 'schedule' }}" + }) +}) + it('warms and probes both Windows images with the persistence job runtime', () => { const job = workflow.jobs['warm-windows'] expect(job.strategy.matrix.os).toEqual( diff --git a/docs/reference/ci-runner-efficiency.md b/docs/reference/ci-runner-efficiency.md index 966cbcdbb10..0b7936aa32d 100644 --- a/docs/reference/ci-runner-efficiency.md +++ b/docs/reference/ci-runner-efficiency.md @@ -1232,3 +1232,127 @@ crash. The last control also proved that crash returns enter the capture. This measures the three-file oracle cohort. Whole-shard timings include other test bodies, imports and transforms, so a whole-suite saving needs separate measurement. + +## Cache warming: let hourly ticks wait for active work + +The hourly warmer previously cancelled an active warmer, even when both used +the same source. On October 2, the [merge-triggered run](https://github.com/stablyai/orca/actions/runs/36965832780) +at 8ff6296 was interrupted by the [hourly run](https://github.com/stablyai/orca/actions/runs/36966367896) +at the same commit. The Windows ARM dependency installation had run for 356 +seconds before cancellation; its native verification was skipped. The other +four lanes had already succeeded. + +Scheduled events now wait in the existing concurrency group. Push, PR and manual +events still replace active work. This keeps one active workflow and the default +single pending slot, using GitHub's documented +[conditional cancellation](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency). +All cache probes, platforms and publication rules remain. + +This avoids the observed discarded installation. It does not remove the next +scheduled run or its repeated successful lanes, and pending replacement still +applies regardless of the cancellation expression. The bounded 20-run sample +contains this collision; it does not establish a recurring or whole-CI saving. + +## Daemon shutdown fixture: remove build tools after compilation + +The fixture now removes compiler and Python build dependencies, plus npm and +node-gyp caches, in the same Docker layer that installs node-pty. It restores the base image's manual +package marks, keeps procps and util-linux, and retains the packages owning the +shared libraries used by Node and the actually loaded PTY addon. This extends +the [official Node image's package-ownership approach](https://github.com/nodejs/docker-node/blob/main/22/bookworm-slim/Dockerfile) +to native addons. Dependency checks and a real PTY spawn fail the build if cleanup +breaks the runtime. + +The [hosted comparison](https://github.com/stablyai/orca/actions/runs/36969120786) +used Ubuntu x64, Docker 28.0.4 and the same resolved Node 22.23.3 base digest for +both images. All 3,418 common entries under `/usr/local` retained their bytes, +modes and symlink targets; all seven resolved runtime libraries matched. Only +two directory-only Python paths disappeared. The 52 removed Debian packages +were build dependencies; retained package versions stayed identical. + +| Image payload | Baseline | Candidate | Reduction | +| ------------------ | ------------- | ------------- | --------- | +| Docker archive | 714,643,456 B | 329,967,616 B | 53.8% | +| Compressed archive | 205,819,558 B | 91,888,840 B | 55.4% | + +Each timed arm started a separate Docker daemon with an empty image store, +decompressed the archive, loaded it, rebuilt from its inline cache and ran the +unchanged descendant/canary shutdown check. Every provisioning layer was cached. + +| Pair/order | Baseline | Candidate | Saving | +| ------------------ | -------- | --------- | ------- | +| 1: baseline first | 16.320 s | 10.198 s | 6.122 s | +| 2: candidate first | 16.389 s | 10.141 s | 6.248 s | +| 3: baseline first | 16.351 s | 10.223 s | 6.129 s | +| Median | 16.351 s | 10.198 s | 6.153 s | + +Median decompression fell from 1.059 to 0.493 seconds and loading from 10.723 to +5.125 seconds. Cached rebuild and shutdown times stayed close. Both seed images +and all six restored consumers passed the original shutdown/canary assertions; +both deliberate no-op disposal controls failed with the descendant still live. +Byte and retained-directory mode faults also failed the inventory comparator. +All six owned daemons stopped gracefully without a forced kill. + +These private daemons used separate classic overlay2 stores and the untouched +host containerd service. Production storage settings were not captured, the +filesystem cache was not flushed, and network transfer is excluded. Production +restores overlap dependency installation, so this 37.6% fixture-sequence saving +does not establish a six-second PR wall-time improvement. Single cold image +builds took 19.313 and 22.623 seconds. The Dockerfile change creates one new +fixture key; the existing main warmer seeds it after merge. + +## WebRTC egress fixture: avoid GPU initialization for the data channel + +The Linux-only probe disables hardware acceleration before Electron readiness. +It still creates two independent processes/profiles, a real data channel, offer +and local description, and checks the exact proxy and UDP policy. The three-second +host observation, 500 ms drain and 20/30/45-second deadlines remain unchanged. + +Two fresh Ubuntu x64 runners compared three alternating pairs each, with identical +phase instrumentation. The [first trial](https://github.com/stablyai/orca/actions/runs/36967511524) +started with the baseline; the [second trial](https://github.com/stablyai/orca/actions/runs/36969120786) +started with the candidate. Their first baseline peer constructors took 4.059 +and 2.546 seconds and logged the GPU command-buffer error seen in an earlier +package timeout. In the second trial, that baseline delay followed the cold +candidate's 2.3 ms constructor. Every candidate constructor took 2.0–2.6 ms. + +Typical process time stayed near 8.3 seconds: baseline/candidate medians were +8.288/8.273 seconds in the first trial and 8.298/8.380 in the reverse trial. +The evidence supports removing an avoidable startup delay, without a measured +typical throughput gain or an estimate of future timeout frequency. + +All 12 full case invocations preserved actual unprotected UDP and zero protected +UDP. Both trials rejected seven faults: missing policy, a packet at 2.9 seconds, +a packet during the drain, a missing peer factory or local description, a broken +packet counter and a hung renderer. The original assertions and deadlines caught +each fault. The normal package gate runs the uninstrumented fixture. + +## Remote resync fixture: keep coalesced frames in one decoder pass + +The first [full PR run](https://github.com/stablyai/orca/actions/runs/36971375340) +passed both package jobs but failed one remote-workspace ordering assertion: +it observed revisions `[2, 3]` where the fixture expected `[3]`. The decoder can +yield between two frames after its four-millisecond work budget. Under slow +scheduling, the first response's promise can publish revision 2 before the +second frame's revision 3 notification is dispatched. + +The fixture now holds its delivery clock at the actual timestamp from multiplexer +construction through the first coalesced-buffer delivery, following the existing +decoder test pattern. It restores the clock before asynchronous assertions and +again before disposal. All four source/order cases retain their exact cache, +publication, client-identity and follow-up-read assertions. Production decoding, +its fairness budget and remote messages are unchanged. + +Normal focused runs passed all 18 tests. Advancing the clock by four milliseconds +per call reproduced `[2, 3]` in both original response-first cases; the fixed +fixture passed all 18 under the same control. Removing the freeze reproduced both +failures. Bypassing the production read-safety guard still caused `[3, 2]` rollback +in all four ordering cases and eight failing tests overall. All controls preserved +the same 18 test identities. This corrects a reproducible fixture assumption; +one CI failure does not establish a failure-rate reduction. + +## Windows server cache metadata: retain the current key + +The bounded 50-head main sample ending at 8ff6296 contained no root package +metadata changes. Removing app-version metadata from the Windows server cache +key would not improve reuse in that sample, so the key remains unchanged. diff --git a/src/main/browser/browser-route-webrtc-egress.electron.test.ts b/src/main/browser/browser-route-webrtc-egress.electron.test.ts index f561ec500ee..1dc9b5ac9a1 100644 --- a/src/main/browser/browser-route-webrtc-egress.electron.test.ts +++ b/src/main/browser/browser-route-webrtc-egress.electron.test.ts @@ -32,6 +32,8 @@ afterAll(() => { function probeMain(resultPath: string, protectedGuest: boolean): string { return ` const { app, BrowserWindow, session } = require('electron') +// This data-channel probe does not need Linux GPU initialization. +if (process.platform === 'linux') app.disableHardwareAcceleration() const dgram = require('node:dgram') const net = require('node:net') const os = require('node:os') diff --git a/src/main/ipc/remote-workspace-stale-resync.test.ts b/src/main/ipc/remote-workspace-stale-resync.test.ts index 07950563a94..b87a9a9893f 100644 --- a/src/main/ipc/remote-workspace-stale-resync.test.ts +++ b/src/main/ipc/remote-workspace-stale-resync.test.ts @@ -321,6 +321,8 @@ describe('workspace.stale resync', () => { 'never publishes an older read after a newer $source notification ($order)', async ({ source, order }) => { let receive: ((data: Buffer) => void) | undefined + // Keep both frames in one decoder pass for the ordering assertions. + const deliveryClock = vi.spyOn(Date, 'now').mockReturnValue(Date.now()) const mux = new SshChannelMultiplexer({ write: () => {}, onData: (callback) => { @@ -349,6 +351,7 @@ describe('workspace.stale resync', () => { receive?.( Buffer.concat(messages.map((message, index) => encodeJsonRpcFrame(message, index + 1, 0))) ) + deliveryClock.mockRestore() if (source === 'own') { expect(getCachedRemoteWorkspaceSnapshot('target-1')?.hostObservationToken).toBe( initial.hostObservationToken @@ -365,6 +368,7 @@ describe('workspace.stale resync', () => { expect(sent.map((event) => event.snapshot.revision)).toEqual([3]) expect(sent[0].sourceClientId).toBe(sourceClientId) } finally { + deliveryClock.mockRestore() mux.dispose() } } From 13ecf051c3d14ceb5e5c798a06a660be27d6a93b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:31:57 -0700 Subject: [PATCH 02/25] Reuse prepared Windows native builds in SSH CI (#24555) * ci: reuse qualified Windows server slots for SSH host tests * ci: reuse prepared relay addons after an exact native cache hit --- .../prepare-orcad-prebuilds/action.yml | 67 ++++++++ .github/workflows/node-server-tests.yml | 59 ++----- .github/workflows/ssh-windows-hosts.yml | 23 ++- ...build-windows-process-tree-relay-addon.mjs | 26 +++- config/scripts/node-server-change-scope.mjs | 1 + .../scripts/node-server-change-scope.test.mjs | 2 + config/scripts/node-server-qualification.mjs | 3 +- .../node-server-qualification.test.mjs | 1 + .../scripts/orcad-windows-prebuild-cache.mjs | 1 + .../orcad-windows-prebuild-workflow.test.mjs | 123 ++++++++++++--- ...ay-windows-process-tree-prepared-addon.mjs | 37 +++++ ...ndows-process-tree-prepared-addon.test.mjs | 145 ++++++++++++++++++ docs/reference/ci-runner-efficiency.md | 36 +++++ 13 files changed, 446 insertions(+), 78 deletions(-) create mode 100644 .github/actions/prepare-orcad-prebuilds/action.yml create mode 100644 config/scripts/relay-windows-process-tree-prepared-addon.mjs create mode 100644 config/scripts/relay-windows-process-tree-prepared-addon.test.mjs diff --git a/.github/actions/prepare-orcad-prebuilds/action.yml b/.github/actions/prepare-orcad-prebuilds/action.yml new file mode 100644 index 00000000000..635fb9d6d54 --- /dev/null +++ b/.github/actions/prepare-orcad-prebuilds/action.yml @@ -0,0 +1,67 @@ +name: Prepare server native prebuilds +description: Builds and smokes the current server slot, optionally restoring a validated Windows slot. + +inputs: + resolve-windows-cache: + description: Resolve the exact Windows cache identity for restoration or later qualified publication. + default: 'false' + restore-windows-cache: + description: Restore the exact Windows slot when its identity is available. + default: 'false' + +outputs: + cache-key: + description: Exact Windows server cache key. + value: ${{ steps.orcad-prebuild-cache-identity.outputs.key }} + cache-path: + description: Directory containing the Windows slot and its manifest. + value: ${{ steps.orcad-prebuild-cache-identity.outputs.path }} + cache-identity-outcome: + description: Whether Windows cache identity resolution succeeded. + value: ${{ steps.orcad-prebuild-cache-identity.outcome }} + cache-hit: + description: Whether the exact Windows slot was restored. + value: ${{ steps.orcad-prebuild-cache-restore.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Resolve this Windows server prebuild cache + id: orcad-prebuild-cache-identity + if: >- + inputs.resolve-windows-cache == 'true' && runner.os == 'Windows' && + (runner.arch == 'X64' || runner.arch == 'ARM64') + continue-on-error: true + shell: bash + run: node config/scripts/orcad-windows-prebuild-cache.mjs --fingerprint + + - name: Restore the exact Windows server prebuild + id: orcad-prebuild-cache-restore + if: >- + inputs.restore-windows-cache == 'true' && + steps.orcad-prebuild-cache-identity.outcome == 'success' && + steps.orcad-prebuild-cache-identity.outputs.key != '' + continue-on-error: true + uses: actions/cache/restore@v5 + with: + path: ${{ steps.orcad-prebuild-cache-identity.outputs.path }} + key: ${{ steps.orcad-prebuild-cache-identity.outputs.key }} + + - name: Build and smoke this runner's node-pty prebuild slot under the pinned Node + shell: bash + env: + WINDOWS_PREBUILD_CACHE_HIT: ${{ steps.orcad-prebuild-cache-restore.outputs.cache-hit }} + WINDOWS_PREBUILD_CACHE_RESTORE_OUTCOME: ${{ steps.orcad-prebuild-cache-restore.outcome }} + run: | + if [ "$RUNNER_OS" = Windows ] && [ "$WINDOWS_PREBUILD_CACHE_HIT" = true ] && + [ "$WINDOWS_PREBUILD_CACHE_RESTORE_OUTCOME" = success ] && + node config/scripts/orcad-windows-prebuild-cache.mjs --validate; then + echo 'Using the validated Windows server prebuild' + else + if [ "$RUNNER_OS" = Windows ]; then + rm -rf -- out/orcad-prebuilds + fi + pnpm build:orcad-prebuilds + fi + pnpm build:orcad-prebuilds --require-slots "$(node config/scripts/build-orcad-prebuilds.mjs --print-slot)" + pnpm build:orcad-prebuilds --smoke diff --git a/.github/workflows/node-server-tests.yml b/.github/workflows/node-server-tests.yml index 449ad212864..d8d6802576c 100644 --- a/.github/workflows/node-server-tests.yml +++ b/.github/workflows/node-server-tests.yml @@ -17,6 +17,7 @@ on: - '.pnpmfile.cjs' - '.github/actions/install-node-dependencies/**' - '.github/actions/prepare-native-runtime/**' + - '.github/actions/prepare-orcad-prebuilds/**' - '.github/workflows/node-server-tests.yml' # Relevant main pushes qualify every platform after the dependency check. push: @@ -35,6 +36,7 @@ on: - '.pnpmfile.cjs' - '.github/actions/install-node-dependencies/**' - '.github/actions/prepare-native-runtime/**' + - '.github/actions/prepare-orcad-prebuilds/**' - '.github/workflows/node-server-tests.yml' workflow_dispatch: inputs: @@ -132,48 +134,15 @@ jobs: - uses: ./.github/actions/install-node-dependencies with: native-runtime: ${{ runner.os == 'Windows' && 'node' || 'none' }} - - name: Resolve this Windows server prebuild cache - id: orcad-prebuild-cache-identity - if: >- - runner.os == 'Windows' && (runner.arch == 'X64' || runner.arch == 'ARM64') && - !inputs.build_template && inputs.ref == '' && - (github.event_name == 'pull_request' || - (github.ref == 'refs/heads/main' && contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name))) - continue-on-error: true - shell: bash - run: node config/scripts/orcad-windows-prebuild-cache.mjs --fingerprint - - name: Restore the exact Windows server prebuild - id: orcad-prebuild-cache-restore - if: >- - (github.event_name == 'pull_request' || github.event_name == 'push') && - steps.orcad-prebuild-cache-identity.outcome == 'success' && - steps.orcad-prebuild-cache-identity.outputs.key != '' - continue-on-error: true - uses: actions/cache/restore@v5 + # Linux release slots come from the floor and Alpine lanes; this slot serves local tests. + - uses: ./.github/actions/prepare-orcad-prebuilds + id: orcad-prebuild with: - path: ${{ steps.orcad-prebuild-cache-identity.outputs.path }} - key: ${{ steps.orcad-prebuild-cache-identity.outputs.key }} - # Before setup-node 18: the scripts import the TypeScript runtime pin. - # Linux release slots come from the Ubuntu 20.04 and Alpine lanes below, where their libc - # floors live; this runner's slot only packages orcad for its own tests. - - name: Build and smoke this runner's node-pty prebuild slot under the pinned Node - shell: bash - env: - WINDOWS_PREBUILD_CACHE_HIT: ${{ steps.orcad-prebuild-cache-restore.outputs.cache-hit }} - WINDOWS_PREBUILD_CACHE_RESTORE_OUTCOME: ${{ steps.orcad-prebuild-cache-restore.outcome }} - run: | - if [ "$RUNNER_OS" = Windows ] && [ "$WINDOWS_PREBUILD_CACHE_HIT" = true ] && - [ "$WINDOWS_PREBUILD_CACHE_RESTORE_OUTCOME" = success ] && - node config/scripts/orcad-windows-prebuild-cache.mjs --validate; then - echo 'Using the validated Windows server prebuild' - else - if [ "$RUNNER_OS" = Windows ]; then - rm -rf -- out/orcad-prebuilds - fi - pnpm build:orcad-prebuilds - fi - pnpm build:orcad-prebuilds --require-slots "$(node config/scripts/build-orcad-prebuilds.mjs --print-slot)" - pnpm build:orcad-prebuilds --smoke + resolve-windows-cache: >- + ${{ !inputs.build_template && inputs.ref == '' && + (github.event_name == 'pull_request' || + (github.ref == 'refs/heads/main' && contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name))) }} + restore-windows-cache: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} - run: pnpm build:orcad # Design D7 upgrade and rollback: the last Bun orcad, built from a main commit that shipped # it, beside this checkout's Node slot; the live-terminal hand-over skips once PROTOCOL_VERSION @@ -229,13 +198,13 @@ jobs: success() && runner.os == 'Windows' && (runner.arch == 'X64' || runner.arch == 'ARM64') && !inputs.build_template && inputs.ref == '' && github.ref == 'refs/heads/main' && contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name) && - steps.orcad-prebuild-cache-identity.outcome == 'success' && - steps.orcad-prebuild-cache-identity.outputs.key != '' + steps.orcad-prebuild.outputs.cache-identity-outcome == 'success' && + steps.orcad-prebuild.outputs.cache-key != '' continue-on-error: true uses: actions/cache/save@v5 with: - path: ${{ steps.orcad-prebuild-cache-identity.outputs.path }} - key: ${{ steps.orcad-prebuild-cache-identity.outputs.key }} + path: ${{ steps.orcad-prebuild.outputs.cache-path }} + key: ${{ steps.orcad-prebuild.outputs.cache-key }} # Linux release slots come from the floor and Alpine lanes; these runners own the rest. - name: Keep this runner's qualified slot for the desktop template if: inputs.build_template && runner.os != 'Linux' diff --git a/.github/workflows/ssh-windows-hosts.yml b/.github/workflows/ssh-windows-hosts.yml index fc427e28940..a677bd8589e 100644 --- a/.github/workflows/ssh-windows-hosts.yml +++ b/.github/workflows/ssh-windows-hosts.yml @@ -28,12 +28,15 @@ on: - 'config/patches/@vscode__windows-process-tree*' - 'config/scripts/build-windows-process-tree-relay-addon.mjs' - 'config/scripts/relay-windows-process-tree-staging.mjs' + - 'config/scripts/relay-windows-process-tree-prepared-addon*.mjs' - 'config/scripts/windows-process-tree-gyp-rebuild.mjs' - 'src/shared/relay-windows-breakaway-launch.ts' - '!src/**/*.test.ts' - 'src/main/ssh/ssh-relay-windows-host-lane.test.ts' - 'config/ci/windows-ssh-provider/**' - '.github/workflows/ssh-windows-hosts.yml' + - '.github/actions/prepare-orcad-prebuilds/**' + - 'config/scripts/orcad-windows-prebuild-cache.mjs' workflow_dispatch: inputs: cells: @@ -81,6 +84,7 @@ jobs: with: persist-credentials: false - uses: ./.github/actions/install-node-dependencies + id: dependencies with: native-runtime: node - name: Self-test the provisioning scripts before touching the machine @@ -95,15 +99,26 @@ jobs: # The deploy materializes rung A from this template; only this runner's slot exists here. # The process-tree addon carries the launcher that starts the relay outside sshd's job; the # orcad slot and the relay both stage it, and a standard-user host has no other launch route. - - name: Build this runner's orcad slot, the win32 template and the relay + - name: Build this runner's Windows process-table addon + shell: bash + env: + REUSE_PREPARED_RUNTIME: ${{ github.event_name == 'pull_request' && steps.dependencies.outputs.native-cache-hit == 'true' }} + run: | + reuse_args=() + if [ "$REUSE_PREPARED_RUNTIME" = true ]; then + reuse_args+=(--reuse-prepared-runtime) + fi + node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=${{ matrix.arch }} "${reuse_args[@]}" + - uses: ./.github/actions/prepare-orcad-prebuilds + with: + resolve-windows-cache: ${{ github.event_name == 'pull_request' }} + restore-windows-cache: ${{ github.event_name == 'pull_request' }} + - name: Build the win32 template and the relay shell: bash env: ORCA_REQUIRE_RELAY_NATIVE_ADDONS: ${{ matrix.arch }} run: | - node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=${{ matrix.arch }} - pnpm build:orcad-prebuilds pnpm build:orcad-prebuilds --require-slots "win32-${{ matrix.arch }}" - pnpm build:orcad-prebuilds --smoke node config/scripts/build-orcad-template.mjs --targets "win32-${{ matrix.arch }}" pnpm run build:relay - name: Run the Windows host cells against a private ${{ matrix.server }} sshd diff --git a/config/scripts/build-windows-process-tree-relay-addon.mjs b/config/scripts/build-windows-process-tree-relay-addon.mjs index bd810144b8a..7ce52837c18 100644 --- a/config/scripts/build-windows-process-tree-relay-addon.mjs +++ b/config/scripts/build-windows-process-tree-relay-addon.mjs @@ -22,6 +22,7 @@ import { execFileSync } from 'node:child_process' import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { createRequire } from 'node:module' import { join, resolve } from 'node:path' +import { canReusePreparedRelayAddon } from './relay-windows-process-tree-prepared-addon.mjs' import { RELAY_WINDOWS_PROCESS_TREE_FILENAME } from '../../src/shared/relay-artifacts.ts' import { ensureWindowsProcessTreeCommandLinePatch, @@ -410,14 +411,15 @@ function applyWindowsProcessTreeBuildFixes() { const repairedCreationTime = repairCreationTimeSources() stageWindowsProcessTreeNodeAddonApiHeaders(PACKAGE_DIR) const repairedCommandLine = ensureWindowsProcessTreeCommandLinePatch(PACKAGE_DIR) - if ( + const repaired = bindingGyp !== originalBinding || processCc !== originalProcess || repairedCommandLine || repairedCreationTime - ) { + if (repaired) { console.warn('[windows-process-tree] Repaired un-applied pnpm patch hunks before build.') } + return repaired } function main() { @@ -431,14 +433,24 @@ function main() { if (!existsSync(PACKAGE_DIR)) { throw new Error(`${PACKAGE_DIR} is missing. Run pnpm install first.`) } - applyWindowsProcessTreeBuildFixes() + const sourceRepaired = applyWindowsProcessTreeBuildFixes() assertPatchApplied() - const gyp = nodeGypRebuildInvocation(arch) - console.log(`[windows-process-tree] building ${arch} from ${gyp.cwd}`) - execFileSync(process.execPath, gyp.args, { cwd: gyp.cwd, stdio: 'inherit' }) - const built = join(PACKAGE_DIR, 'build', 'Release', 'windows_process_tree.node') + if ( + canReusePreparedRelayAddon({ + enabled: process.argv.includes('--reuse-prepared-runtime'), + arch, + addonPath: built, + sourceRepaired + }) + ) { + console.log(`[windows-process-tree] reusing the prepared ${arch} addon`) + } else { + const gyp = nodeGypRebuildInvocation(arch) + console.log(`[windows-process-tree] building ${arch} from ${gyp.cwd}`) + execFileSync(process.execPath, gyp.args, { cwd: gyp.cwd, stdio: 'inherit' }) + } if (!existsSync(built)) { throw new Error(`node-gyp reported success but ${built} is missing.`) } diff --git a/config/scripts/node-server-change-scope.mjs b/config/scripts/node-server-change-scope.mjs index 342e5ad7c3a..7badba9fa98 100644 --- a/config/scripts/node-server-change-scope.mjs +++ b/config/scripts/node-server-change-scope.mjs @@ -43,6 +43,7 @@ const ALWAYS_FILES = new Set([ const ALWAYS_PREFIXES = [ '.github/actions/install-node-dependencies/', '.github/actions/prepare-native-runtime/', + '.github/actions/prepare-orcad-prebuilds/', // These areas also contain worker paths and fixtures opened without an import. 'src/main/persistence/', 'src/main/sqlite/', diff --git a/config/scripts/node-server-change-scope.test.mjs b/config/scripts/node-server-change-scope.test.mjs index 6c1903f8031..0ff118ed678 100644 --- a/config/scripts/node-server-change-scope.test.mjs +++ b/config/scripts/node-server-change-scope.test.mjs @@ -84,6 +84,7 @@ it.each([ 'native/windows-registry/src/addon.cc', '.github/actions/install-node-dependencies/action.yml', '.github/actions/prepare-native-runtime/action.yml', + '.github/actions/prepare-orcad-prebuilds/action.yml', '.github/workflows/node-server-tests.yml', 'src/main/persistence/profile-state/new-worker.ts' ])('always selects build, native and dynamically opened inputs: %s', async (file) => { @@ -113,6 +114,7 @@ describe('the actual Bun build and profile-test dependency graph', () => { 'src/main/worker-thread-entry-path.ts', 'config/scripts/zip-extractor-command.mjs', 'config/scripts/windows-process-tree-gyp-rebuild.mjs', + 'config/scripts/relay-windows-process-tree-prepared-addon.mjs', 'config/scripts/orcad-windows-prebuild-cache.mjs', 'config/scripts/profile-state-worker-smoke.mjs', 'config/scripts/vitest-host-ports-setup.ts', diff --git a/config/scripts/node-server-qualification.mjs b/config/scripts/node-server-qualification.mjs index dc409a1c84d..9901b609a9b 100644 --- a/config/scripts/node-server-qualification.mjs +++ b/config/scripts/node-server-qualification.mjs @@ -12,7 +12,8 @@ const BUILD_PREFIXES = [ 'native/', 'config/patches/', '.github/actions/install-node-dependencies/', - '.github/actions/prepare-native-runtime/' + '.github/actions/prepare-native-runtime/', + '.github/actions/prepare-orcad-prebuilds/' ] // A remote target's OS does not identify the client platform that builds its commands. const CROSS_HOST_PREFIXES = ['src/main/ssh/', 'src/main/providers/', 'src/relay/'] diff --git a/config/scripts/node-server-qualification.test.mjs b/config/scripts/node-server-qualification.test.mjs index 9c3c0bd7b27..6018fe6a7e4 100644 --- a/config/scripts/node-server-qualification.test.mjs +++ b/config/scripts/node-server-qualification.test.mjs @@ -28,6 +28,7 @@ it.each([ 'config/patches/node-pty.patch', '.github/actions/install-node-dependencies/action.yml', '.github/actions/prepare-native-runtime/action.yml', + '.github/actions/prepare-orcad-prebuilds/action.yml', 'src/main/ssh/ssh-provider.ts', 'src/main/providers/local-pty-provider.ts', 'src/shared/child-process/run-process.ts', diff --git a/config/scripts/orcad-windows-prebuild-cache.mjs b/config/scripts/orcad-windows-prebuild-cache.mjs index 31c617ea1a6..2cd7d7636b1 100644 --- a/config/scripts/orcad-windows-prebuild-cache.mjs +++ b/config/scripts/orcad-windows-prebuild-cache.mjs @@ -19,6 +19,7 @@ const root = resolve(import.meta.dirname, '../..') const ownership = require('./node-pty-job-ownership.cjs') export const WINDOWS_PREBUILD_CACHE_INPUTS = [ 'package.json', + '.github/actions/prepare-orcad-prebuilds/action.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'config/patches/node-pty@1.1.0.patch', diff --git a/config/scripts/orcad-windows-prebuild-workflow.test.mjs b/config/scripts/orcad-windows-prebuild-workflow.test.mjs index 3d42ffd8df3..3e6c1199e3c 100644 --- a/config/scripts/orcad-windows-prebuild-workflow.test.mjs +++ b/config/scripts/orcad-windows-prebuild-workflow.test.mjs @@ -8,28 +8,54 @@ import { runProcessSync } from './script-child-process.mjs' const workflow = parse(readFileSync('.github/workflows/node-server-tests.yml', 'utf8')) const steps = workflow.jobs.persistence.steps -const identity = steps.find((step) => step.id === 'orcad-prebuild-cache-identity') -const restore = steps.find((step) => step.id === 'orcad-prebuild-cache-restore') -const build = steps.find((step) => step.name?.startsWith('Build and smoke this runner')) +const prepare = steps.find((step) => step.id === 'orcad-prebuild') +const action = parse(readFileSync('.github/actions/prepare-orcad-prebuilds/action.yml', 'utf8')) +const actionSteps = action.runs.steps +const identity = actionSteps.find((step) => step.id === 'orcad-prebuild-cache-identity') +const restore = actionSteps.find((step) => step.id === 'orcad-prebuild-cache-restore') +const build = actionSteps.find((step) => step.name?.startsWith('Build and smoke this runner')) const save = steps.find((step) => step.uses === 'actions/cache/save@v5') function evaluate(expression, context) { return runInNewContext( - expression.replaceAll( - 'steps.orcad-prebuild-cache-identity', - 'steps["orcad-prebuild-cache-identity"]' - ), + expression + .replaceAll('steps.orcad-prebuild-cache-identity', 'steps["orcad-prebuild-cache-identity"]') + .replaceAll('steps.orcad-prebuild', 'steps["orcad-prebuild"]') + .replace( + /\.(resolve-windows-cache|restore-windows-cache|cache-identity-outcome|cache-key)\b/g, + (_match, name) => `["${name}"]` + ), context ) } +function actionContext(ctx, caller = prepare) { + return { + ...ctx, + inputs: Object.fromEntries( + Object.entries(caller.with).map(([key, value]) => [ + key, + value.startsWith('${{') ? String(evaluate(value.slice(3, -2).trim(), ctx)) : value + ]) + ) + } +} + +function setIdentity(ctx, outcome, key = 'exact-key') { + ctx.steps['orcad-prebuild-cache-identity'] = { outcome, outputs: { key } } + ctx.steps['orcad-prebuild'].outputs = { 'cache-identity-outcome': outcome, 'cache-key': key } +} + function context(os, arch, event, ref, template = false) { return { runner: { os, arch }, github: { event_name: event, ref }, inputs: { build_template: template, ref: '' }, steps: { - 'orcad-prebuild-cache-identity': { outcome: 'success', outputs: { key: 'exact-key' } } + 'orcad-prebuild-cache-identity': { outcome: 'success', outputs: { key: 'exact-key' } }, + 'orcad-prebuild': { + outputs: { 'cache-identity-outcome': 'success', 'cache-key': 'exact-key' } + } }, success: () => true, fromJSON: JSON.parse, @@ -58,10 +84,10 @@ describe('Windows server prebuild cache workflow', () => { ]) { const ctx = context(os, arch, event, ref, template) const windows = os === 'Windows' && ['X64', 'ARM64'].includes(arch) - const resolves = evaluate(identity.if, ctx) + const resolves = evaluate(identity.if, actionContext(ctx)) expect(resolves).toBe(windows && (reads || writes)) - ctx.steps['orcad-prebuild-cache-identity'].outcome = resolves ? 'success' : 'skipped' - expect(evaluate(restore.if, ctx)).toBe(windows && reads) + setIdentity(ctx, resolves ? 'success' : 'skipped') + expect(evaluate(restore.if, actionContext(ctx))).toBe(windows && reads) expect(evaluate(save.if, ctx)).toBe(windows && writes) ctx.success = () => false expect(evaluate(save.if, ctx)).toBe(false) @@ -72,15 +98,15 @@ describe('Windows server prebuild cache workflow', () => { for (const event of ['pull_request', 'push']) { const ctx = context('Windows', 'X64', event, 'refs/heads/main') for (const outcome of ['failure', 'skipped']) { - ctx.steps['orcad-prebuild-cache-identity'].outcome = outcome - expect(evaluate(restore.if, ctx)).toBe(false) + setIdentity(ctx, outcome) + expect(evaluate(restore.if, actionContext(ctx))).toBe(false) expect(evaluate(save.if, ctx)).toBe(false) } - ctx.steps['orcad-prebuild-cache-identity'] = { outcome: 'success', outputs: { key: '' } } - expect(evaluate(restore.if, ctx)).toBe(false) + setIdentity(ctx, 'success', '') + expect(evaluate(restore.if, actionContext(ctx))).toBe(false) expect(evaluate(save.if, ctx)).toBe(false) ctx.inputs.ref = 'refs/tags/v1' - expect(evaluate(identity.if, ctx)).toBe(false) + expect(evaluate(identity.if, actionContext(ctx))).toBe(false) expect(evaluate(save.if, ctx)).toBe(false) } }) @@ -95,10 +121,10 @@ describe('Windows server prebuild cache workflow', () => { ]) { const ctx = context('Windows', 'X64', event, 'refs/heads/main') ctx.inputs = inputs - expect(evaluate(identity.if, ctx)).toBe(false) + expect(evaluate(identity.if, actionContext(ctx))).toBe(false) expect(evaluate(save.if, ctx)).toBe(false) - ctx.steps['orcad-prebuild-cache-identity'].outcome = 'skipped' - expect(evaluate(restore.if, ctx)).toBe(false) + setIdentity(ctx, 'skipped') + expect(evaluate(restore.if, actionContext(ctx))).toBe(false) } } ) @@ -109,11 +135,17 @@ describe('Windows server prebuild cache workflow', () => { expect(restore.uses).toBe('actions/cache/restore@v5') expect(restore['continue-on-error']).toBe(true) expect(restore.with['restore-keys']).toBeUndefined() - expect(restore.with).toEqual(save.with) + expect(action.outputs['cache-path'].value).toBe(restore.with.path) + expect(action.outputs['cache-key'].value).toBe(restore.with.key) + expect(save.with.path).toBe('${{ steps.orcad-prebuild.outputs.cache-path }}') + expect(save.with.key).toBe('${{ steps.orcad-prebuild.outputs.cache-key }}') + expect(prepare.uses).toBe('./.github/actions/prepare-orcad-prebuilds') expect(restore.with.key).toBe('${{ steps.orcad-prebuild-cache-identity.outputs.key }}') expect(restore.with.path).toBe('${{ steps.orcad-prebuild-cache-identity.outputs.path }}') - expect(steps.indexOf(restore)).toBeLessThan(steps.indexOf(build)) + expect(actionSteps.indexOf(restore)).toBeLessThan(actionSteps.indexOf(build)) expect(save['continue-on-error']).toBe(true) + expect(steps.indexOf(save)).toBeGreaterThan(steps.indexOf(prepare)) + expect(actionSteps.some((step) => step.uses === 'actions/cache/save@v5')).toBe(false) expect(build.if).toBeUndefined() expect(build['continue-on-error']).toBeUndefined() for (const gate of steps.filter((step) => @@ -137,6 +169,55 @@ describe('Windows server prebuild cache workflow', () => { }) }) +describe('SSH Windows consumers of qualified server slots', () => { + const sshWorkflow = parse(readFileSync('.github/workflows/ssh-windows-hosts.yml', 'utf8')) + const sshSteps = sshWorkflow.jobs.hosts.steps + const sshPrepare = sshSteps.find((step) => step.uses === prepare.uses) + + it.each(['pull_request', 'workflow_dispatch'])( + 'restores only PR slots and keeps manual %s qualification fresh', + (event) => { + for (const arch of ['X64', 'ARM64']) { + const ctx = context('Windows', arch, event, 'refs/heads/main') + expect(evaluate(identity.if, actionContext(ctx, sshPrepare))).toBe(event === 'pull_request') + expect(evaluate(restore.if, actionContext(ctx, sshPrepare))).toBe(event === 'pull_request') + } + expect(sshSteps.some((step) => step.uses === 'actions/cache/save@v5')).toBe(false) + } + ) + + it('keeps both architectures, both sshd versions and the existing build order', () => { + expect( + sshWorkflow.jobs.hosts.strategy.matrix.include.map(({ arch, server }) => [arch, server]) + ).toEqual([ + ['x64', 'inbox'], + ['x64', 'preview'], + ['arm64', 'inbox'], + ['arm64', 'preview'] + ]) + const addon = sshSteps.findIndex((step) => + step.run?.includes('build-windows-process-tree-relay-addon.mjs') + ) + const template = sshSteps.findIndex((step) => step.run?.includes('build-orcad-template.mjs')) + const hosts = sshSteps.findIndex((step) => step.name?.startsWith('Run the Windows host cells')) + expect(addon).toBeLessThan(sshSteps.indexOf(sshPrepare)) + expect(sshSteps.indexOf(sshPrepare)).toBeLessThan(template) + expect(template).toBeLessThan(hosts) + expect(sshSteps[template].env.ORCA_REQUIRE_RELAY_NATIVE_ADDONS).toBe('${{ matrix.arch }}') + expect(sshSteps[template].run).toContain('--require-slots "win32-${{ matrix.arch }}"') + expect(sshSteps[hosts].run).toContain("@('pinned-cmd','pinned-powershell','legacy-opt-out')") + for (const workflowPaths of [ + workflow.on.pull_request.paths, + sshWorkflow.on.pull_request.paths + ]) { + expect(workflowPaths).toContain('.github/actions/prepare-orcad-prebuilds/**') + } + expect(sshWorkflow.on.pull_request.paths).toContain( + 'config/scripts/orcad-windows-prebuild-cache.mjs' + ) + }) +}) + const commandMocks = ` node() { echo "node $*" >> "$COMMAND_LOG" diff --git a/config/scripts/relay-windows-process-tree-prepared-addon.mjs b/config/scripts/relay-windows-process-tree-prepared-addon.mjs new file mode 100644 index 00000000000..b4d9ff4ced5 --- /dev/null +++ b/config/scripts/relay-windows-process-tree-prepared-addon.mjs @@ -0,0 +1,37 @@ +import { join } from 'node:path' +import { runProcessSync } from './script-child-process.mjs' +import { relayWindowsProcessTreeAddonDefect } from './windows-process-tree-gyp-rebuild.mjs' + +const root = join(import.meta.dirname, '../..') + +function checkPreparedRuntime() { + return runProcessSync({ + program: process.execPath, + args: [join(root, 'config/scripts/ensure-native-runtime.mjs'), '--check-only'], + cwd: root, + timeoutMs: 30_000 + }) +} + +export function canReusePreparedRelayAddon({ + enabled = false, + arch, + addonPath, + sourceRepaired, + hostArch = process.arch, + platform = process.platform, + checkRuntime = checkPreparedRuntime +}) { + if (!enabled || sourceRepaired !== false || platform !== 'win32' || arch !== hostArch) { + return false + } + try { + if (relayWindowsProcessTreeAddonDefect(addonPath, arch) !== null) { + return false + } + const result = checkRuntime() + return result.code === 0 && !result.timedOut && !result.outputTruncated + } catch { + return false + } +} diff --git a/config/scripts/relay-windows-process-tree-prepared-addon.test.mjs b/config/scripts/relay-windows-process-tree-prepared-addon.test.mjs new file mode 100644 index 00000000000..106147f9cf5 --- /dev/null +++ b/config/scripts/relay-windows-process-tree-prepared-addon.test.mjs @@ -0,0 +1,145 @@ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { runInNewContext } from 'node:vm' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { parse } from 'yaml' +import { peImage } from './windows-pe-image-fixture.mjs' +import { canReusePreparedRelayAddon } from './relay-windows-process-tree-prepared-addon.mjs' +import { runProcessSync } from './script-child-process.mjs' + +const directories = [] +afterEach(() => { + for (const directory of directories.splice(0)) { + rmSync(directory, { recursive: true, force: true }) + } +}) + +function prepared(arch = 'x64', suffix = 'spawnOutsideJob') { + const directory = mkdtempSync(join(tmpdir(), 'orca-prepared-relay-addon-')) + directories.push(directory) + const addonPath = join(directory, 'process-tree.node') + writeFileSync(addonPath, Buffer.concat([peImage({ arch }), Buffer.from(suffix)])) + return { + enabled: true, + arch, + hostArch: arch, + platform: 'win32', + sourceRepaired: false, + addonPath, + checkRuntime: vi.fn(() => ({ code: 0, timedOut: false, outputTruncated: false })) + } +} + +describe('reusing a prepared Windows relay addon', () => { + it.each(['x64', 'arm64'])( + 'accepts a clean launcher-capable %s addon after its runtime probe', + (arch) => { + const options = prepared(arch) + expect(canReusePreparedRelayAddon(options)).toBe(true) + expect(options.checkRuntime).toHaveBeenCalledOnce() + } + ) + + it.each([ + { enabled: false }, + { enabled: undefined }, + { platform: 'darwin' }, + { platform: 'linux' }, + { hostArch: 'arm64' }, + { sourceRepaired: true }, + { sourceRepaired: undefined } + ])('compiles freshly without current same-host preparation: %j', (overrides) => { + const options = { ...prepared(), ...overrides } + expect(canReusePreparedRelayAddon(options)).toBe(false) + expect(options.checkRuntime).not.toHaveBeenCalled() + }) + + it.each([ + ['upstream reader', 'ReadProcessMemory spawnOutsideJob'], + ['pre-launcher addon', 'getProcessCreationTime'] + ])('refuses the %s before loading it', (_label, suffix) => { + const options = prepared('x64', suffix) + expect(canReusePreparedRelayAddon(options)).toBe(false) + expect(options.checkRuntime).not.toHaveBeenCalled() + }) + + it('refuses missing, truncated and wrong-architecture binaries', () => { + const options = prepared() + for (const bytes of [Buffer.from('spawnOutsideJob'), peImage({ arch: 'arm64' })]) { + writeFileSync(options.addonPath, bytes) + expect(canReusePreparedRelayAddon(options)).toBe(false) + } + rmSync(options.addonPath) + expect(canReusePreparedRelayAddon(options)).toBe(false) + expect(options.checkRuntime).not.toHaveBeenCalled() + }) + + it.each([ + { code: 1, timedOut: false, outputTruncated: false }, + { code: null, timedOut: false, outputTruncated: false }, + { code: 0, timedOut: true, outputTruncated: false }, + { code: 0, timedOut: false, outputTruncated: true } + ])('falls back when native loading or CreationTime validation fails: %j', (result) => { + const options = prepared() + options.checkRuntime.mockReturnValue(result) + expect(canReusePreparedRelayAddon(options)).toBe(false) + }) + + it('falls back on probe exceptions', () => { + const options = prepared() + options.checkRuntime.mockImplementation(() => { + throw new Error('probe spawn failed') + }) + expect(canReusePreparedRelayAddon(options)).toBe(false) + }) +}) + +const workflow = parse(readFileSync('.github/workflows/ssh-windows-hosts.yml', 'utf8')) +const steps = workflow.jobs.hosts.steps +const preparation = steps.find((step) => step.id === 'dependencies') +const build = steps.find((step) => step.name === "Build this runner's Windows process-table addon") + +it.each(['pull_request', 'workflow_dispatch'])( + 'requests reuse only after an exact prepared PR hit under %s', + (event) => { + expect(workflow.on.pull_request.paths).toContain( + 'config/scripts/relay-windows-process-tree-prepared-addon*.mjs' + ) + expect(preparation.with['native-runtime']).toBe('node') + expect(steps.indexOf(preparation)).toBeLessThan(steps.indexOf(build)) + for (const cacheHit of ['true', 'false', '', undefined]) { + const expression = build.env.REUSE_PREPARED_RUNTIME.slice(3, -2).replace( + 'outputs.native-cache-hit', + 'outputs["native-cache-hit"]' + ) + const enabled = runInNewContext(expression, { + github: { event_name: event }, + steps: { dependencies: { outputs: { 'native-cache-hit': cacheHit } } } + }) + expect(enabled).toBe(event === 'pull_request' && cacheHit === 'true') + } + } +) + +it.each(['x64', 'arm64'])('passes the optional reuse flag safely to the %s builder', (arch) => { + for (const enabled of ['true', 'false']) { + const directory = mkdtempSync(join(tmpdir(), 'orca-relay-build-args-')) + directories.push(directory) + const output = join(directory, 'arguments.txt') + const script = `node() { printf '%s\n' "$@" > "$ARGUMENTS_FILE"; }\n${build.run.replaceAll('${{ matrix.arch }}', arch)}` + const result = runProcessSync({ + program: 'bash', + args: ['-e', '-c', script], + cwd: directory, + env: { ...process.env, ARGUMENTS_FILE: output, REUSE_PREPARED_RUNTIME: enabled }, + timeoutMs: 10_000 + }) + expect(result.code, result.stderr).toBe(0) + expect(readFileSync(output, 'utf8').trim().split('\n')).toEqual([ + 'config/scripts/build-windows-process-tree-relay-addon.mjs', + `--arch=${arch}`, + ...(enabled === 'true' ? ['--reuse-prepared-runtime'] : []) + ]) + } +}) diff --git a/docs/reference/ci-runner-efficiency.md b/docs/reference/ci-runner-efficiency.md index 0b7936aa32d..6c7f0d059cf 100644 --- a/docs/reference/ci-runner-efficiency.md +++ b/docs/reference/ci-runner-efficiency.md @@ -34,12 +34,48 @@ The [DNF command reference](https://dnf.readthedocs.io/en/stable/command_ref.htm defines `--disablerepo` as a temporary command-level filter, so later commands retain the image's repository configuration. +The [PR qualification run](https://github.com/stablyai/orca/actions/runs/36972824276/job/110734327685) +passed the x64 floor native smoke and persistence suite. Its prerequisite step +finished within GitHub's one-second timing resolution, compared with 5 minutes +37 seconds in the baseline job; the supplied tools needed no package install. +This measures that step, not the complete workflow or its queue time. + A [completed main run](https://github.com/stablyai/orca/actions/runs/36962172614) used 42 aggregate runner-minutes across 11 test jobs. The [latest daily demand report](https://github.com/stablyai/orca/actions/runs/36965354205) estimates 34.9 headless runner-hours, including 23.4 in cancelled runs. These are baseline observations; post-merge savings have not yet been measured. +## SSH Windows slot reuse + +The SSH Windows host workflow uses the same server-slot preparation action as +headless qualification. Its four PR jobs can restore the exact slot published by +fully qualified main runs, then validate its inventory and hashes and run the +required-slot and pinned-Node smoke checks. Misses or invalid payloads compile +freshly. Only main headless qualification publishes; manual SSH qualification +still builds freshly. Both sshd versions, both architectures, all three host +cells, the process-table addon build, and the template/relay builds remain. + +The action is part of the cache fingerprint, so this extraction starts a new +namespace that needs a successful main seed. The existing hosted measurements +below suggest about 160 aggregate runner-seconds saved across four warm SSH jobs; +that is a conditional estimate, not a measured improvement of this consumer. +Private sshd installation and host execution still dominate this workflow. + +## Prepared relay addon reuse + +A [completed SSH Windows run](https://github.com/stablyai/orca/actions/runs/36972043877) +rebuilt the process-table relay addon after native dependency preparation. From +its builder's start message to the validated staged artifact, x64 took 85.5 seconds +and ARM64 took 135.6 seconds. These are single-run observations, not medians. + +An opt-in reuse path checks the same binary architecture, patched reader and +launcher exports as staging, then runs the existing native-load and CreationTime +probe. Repaired source or incomplete evidence requires a fresh build. SSH PRs +request reuse only following an exact prepared native-cache hit; manual SSH and +all release builders retain fresh compilation. Subsequent staging checks still +run. Hosted validation and the reuse interval remain to be measured. + ## October 1 Windows and dependency cache follow-up [PR #24355](https://github.com/stablyai/orca/pull/24355) merged at `197ea3a3`. From 7b1449acadc91e4401fe2135efab15743da3c39f Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:43:55 -0700 Subject: [PATCH 03/25] fix(ai-vault): honor ZCode transcript sequence order (#24584) --- ...session-scanner-opencode-sqlite-capture.ts | 8 +- .../session-scanner-opencode-sqlite.ts | 19 +-- .../ai-vault/session-scanner-zcode-order.ts | 18 +++ .../ai-vault/session-scanner-zcode.test.ts | 116 ++++++++++++++++++ 4 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 src/main/ai-vault/session-scanner-zcode-order.ts diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-capture.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-capture.ts index ef15b368ef3..818a40ed874 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-capture.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-capture.ts @@ -11,6 +11,7 @@ import { canReadOpenCodeMessageParts } from './session-scanner-opencode-sqlite-s import type { TranscriptMessage, TranscriptMessageRole } from './session-transcript-consumers' import { boundedText, toolCallText } from './session-transcript-message-content' import { zcodeVisibleMessageFilter } from './session-scanner-zcode-visibility' +import { zcodeTranscriptOrder } from './session-scanner-zcode-order' import type SyncDatabase from '../sqlite/sync-database' // Why: the session list needs the newest few messages, and the search index @@ -120,7 +121,7 @@ function captureRole(role: string | null): TranscriptMessageRole | null { return role === 'user' || role === 'assistant' ? role : null } -function buildCaptureQuery(agent: 'opencode' | 'zcode'): string { +function buildCaptureQuery(db: SyncDatabase, agent: 'opencode' | 'zcode'): string { // Message order, then part order within a message: the same key the preview // read uses, run forwards and without the newest-N window. return `SELECT m.id AS message_id, @@ -134,7 +135,8 @@ function buildCaptureQuery(agent: 'opencode' | 'zcode'): string { ${zcodeVisibleMessageFilter(agent)} AND json_extract(m.data, '$.role') IN ('user','assistant') AND json_extract(p.data, '$.type') IN ${OPENCODE_CAPTURE_PART_TYPES} - ORDER BY m.time_created ASC, m.id ASC, p.time_created ASC, p.rowid ASC + ORDER BY ${zcodeTranscriptOrder(db, agent, 'message', 'm', 'ASC')}, + ${zcodeTranscriptOrder(db, agent, 'part', 'p', 'ASC')} LIMIT ?` } @@ -164,7 +166,7 @@ export function readOpenCodeSessionMessages( ) } const rows = db - .prepare(buildCaptureQuery(agent)) + .prepare(buildCaptureQuery(db, agent)) .all(sessionId, OPENCODE_CAPTURE_RECORD_LIMIT + 1) if (rows.length > OPENCODE_CAPTURE_RECORD_LIMIT) { throw new Error( diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite.ts b/src/main/ai-vault/session-scanner-opencode-sqlite.ts index 4e3d8bc74b8..3e2052b2aab 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite.ts @@ -17,6 +17,7 @@ import { } from './session-scanner-opencode-sqlite-schema' import { normalizeTitleText } from './session-scanner-values' import { zcodeVisibleMessageFilter } from './session-scanner-zcode-visibility' +import { zcodeTranscriptOrder } from './session-scanner-zcode-order' import type SyncDatabase from '../sqlite/sync-database' import { columnExists, tableExists } from '../opencode-usage/schema-helpers' @@ -162,11 +163,11 @@ function readFirstUserPromptFromOpenCodeDb( ${zcodeVisibleMessageFilter(agent)} AND json_extract(m.data, '$.role') = 'user' AND json_extract(fp.data, '$.type') = 'text' - ORDER BY m.time_created ASC, m.id ASC + ORDER BY ${zcodeTranscriptOrder(db, agent, 'message', 'm', 'ASC')} LIMIT 1 ) AND json_extract(p.data, '$.type') = 'text' - ORDER BY p.time_created ASC, p.rowid ASC + ORDER BY ${zcodeTranscriptOrder(db, agent, 'part', 'p', 'ASC')} LIMIT ${FIRST_USER_PROMPT_PART_LIMIT}` ) .all(sessionId) @@ -198,15 +199,19 @@ function buildPreviewQuery(db: SyncDatabase, agent: 'opencode' | 'zcode'): strin p.time_created, json_extract(m.data, '$.summary.title') AS summary_title, json_extract(m.data, '$.summary.body') AS summary_body - FROM (SELECT id, data FROM message - WHERE session_id = ? - ${zcodeVisibleMessageFilter(agent, 'data')} - ORDER BY time_created DESC, id DESC + FROM (SELECT ${agent === 'zcode' ? 'm.rowid AS rowid, m.*' : 'm.id, m.data'} FROM message m + WHERE m.session_id = ? + ${zcodeVisibleMessageFilter(agent)} + ORDER BY ${zcodeTranscriptOrder(db, agent, 'message', 'm', 'DESC')} LIMIT ${OPENCODE_SQLITE_PREVIEW_MESSAGE_WINDOW}) m JOIN part p ON p.message_id = m.id WHERE json_extract(m.data, '$.role') IN ('user','assistant') AND json_extract(p.data, '$.type') = 'text' - ORDER BY p.time_created DESC + ORDER BY ${ + agent === 'zcode' + ? `${zcodeTranscriptOrder(db, agent, 'message', 'm', 'DESC')}, ${zcodeTranscriptOrder(db, agent, 'part', 'p', 'DESC')}` + : 'p.time_created DESC' + } LIMIT ?` } diff --git a/src/main/ai-vault/session-scanner-zcode-order.ts b/src/main/ai-vault/session-scanner-zcode-order.ts new file mode 100644 index 00000000000..958f73ff7ca --- /dev/null +++ b/src/main/ai-vault/session-scanner-zcode-order.ts @@ -0,0 +1,18 @@ +import { columnExists } from '../opencode-usage/schema-helpers' +import type SyncDatabase from '../sqlite/sync-database' + +export function zcodeTranscriptOrder( + db: SyncDatabase, + agent: 'opencode' | 'zcode', + table: 'message' | 'part', + alias: 'm' | 'p', + direction: 'ASC' | 'DESC' +): string { + const sequenced = agent === 'zcode' && columnExists(db, table, 'sequence') + const sequence = sequenced + ? `${alias}.sequence IS NULL ${direction}, ${alias}.sequence ${direction}, ` + : '' + // ZCode orders imported/forked transcripts by sequence, with legacy NULL rows last. + const tieBreaker = table === 'message' ? (sequenced ? 'rowid' : 'id') : sequenced ? 'id' : 'rowid' + return `${sequence}${alias}.time_created ${direction}, ${alias}.${tieBreaker} ${direction}` +} diff --git a/src/main/ai-vault/session-scanner-zcode.test.ts b/src/main/ai-vault/session-scanner-zcode.test.ts index 4783b213450..1acade5af17 100644 --- a/src/main/ai-vault/session-scanner-zcode.test.ts +++ b/src/main/ai-vault/session-scanner-zcode.test.ts @@ -57,6 +57,122 @@ afterEach(() => { }) describe('ZCode AI Vault SQLite worker', () => { + it.each(['message', 'part', 'both'] as const)( + 'reads partially migrated %s sequence columns and keeps legacy NULL rows last', + async (tables) => { + const directory = mkdtempSync(join(tmpdir(), 'orca-zcode-legacy-sequence-')) + directories.push(directory) + const dbPath = join(directory, 'db.sqlite') + writeOpenCodeSqliteDatabase(dbPath, [ + { + id: 'legacy-session', + turns: [ + { role: 'user', parts: ['first', 'second', 'legacy part'] }, + { role: 'user', parts: ['legacy message'] } + ] + } + ]) + const db = new SyncDatabase(dbPath) + try { + if (tables !== 'part') { + db.exec(`ALTER TABLE message ADD COLUMN sequence INTEGER; + UPDATE message SET sequence = 0 WHERE rowid = 1; + UPDATE message SET time_created = 1 WHERE rowid = 2;`) + } + if (tables !== 'message') { + db.exec(`ALTER TABLE part ADD COLUMN sequence INTEGER; + UPDATE part SET sequence = rowid WHERE rowid <= 2; + UPDATE part SET time_created = 1 WHERE rowid = 3;`) + } + } finally { + db.close() + } + const captured = await handleOpenCodeSqliteRequest({ + id: 8, + kind: 'capture', + agent: 'zcode', + dbPath, + sessionId: 'legacy-session', + platform: 'win32' + }) + expect(captured.ok).toBe(true) + if (!captured.ok) { + return + } + expect(readMessageTexts(captured.value)).toEqual([ + { text: 'first\nsecond\nlegacy part' }, + { text: 'legacy message' } + ]) + } + ) + + it('uses stored message and part sequence for first prompt, preview, and search', async () => { + const directory = mkdtempSync(join(tmpdir(), 'orca-zcode-sequence-')) + directories.push(directory) + const dbPath = join(directory, 'db.sqlite') + writeOpenCodeSqliteDatabase(dbPath, [ + { + id: 'ordered-session', + turns: [ + { role: 'user', parts: ['first prompt start', 'first prompt end'] }, + { role: 'assistant', parts: ['first response'] }, + { role: 'user', parts: ['second prompt'] }, + { role: 'assistant', parts: ['second response start', 'second response end'] } + ] + } + ]) + const db = new SyncDatabase(dbPath) + try { + db.exec(`ALTER TABLE message ADD COLUMN sequence INTEGER; + ALTER TABLE part ADD COLUMN sequence INTEGER; + UPDATE message SET sequence = rowid, time_created = time_created - rowid * 120000; + UPDATE part SET sequence = rowid, time_created = time_created - rowid * 120000;`) + } finally { + db.close() + } + const parsed = await handleOpenCodeSqliteRequest({ + id: 6, + kind: 'parse', + agent: 'zcode', + dbPath, + sessionId: 'ordered-session', + platform: 'darwin', + fullFirstUserPrompt: true + }) + expect(parsed).toMatchObject({ + ok: true, + value: { + firstUserPrompt: 'first prompt start\nfirst prompt end', + previewMessagesTruncated: true, + previewMessages: [ + { text: 'first prompt end' }, + { text: 'first response' }, + { text: 'second prompt' }, + { text: 'second response start' }, + { text: 'second response end' } + ] + } + }) + const captured = await handleOpenCodeSqliteRequest({ + id: 7, + kind: 'capture', + agent: 'zcode', + dbPath, + sessionId: 'ordered-session', + platform: 'darwin' + }) + expect(captured.ok).toBe(true) + if (!captured.ok) { + return + } + expect(readMessageTexts(captured.value)).toEqual([ + { text: 'first prompt start\nfirst prompt end' }, + { text: 'first response' }, + { text: 'second prompt' }, + { text: 'second response start\nsecond response end' } + ]) + }) + it('lists, parses, and captures ZCode sessions without labelling them OpenCode', async () => { const directory = mkdtempSync(join(tmpdir(), 'orca-zcode-ai-vault-')) directories.push(directory) From 0a81211994348e63e040a3af6d3fe1f885858d98 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:48:33 -0700 Subject: [PATCH 04/25] fix(cursor): read desktop login outside the main thread (#24572) * fix(cursor): read desktop login outside the main thread * fix(cursor): await native worker retirement before respawning --- .../build-plugins/plain-node-entry-guard.ts | 1 + config/electron-builder.config.cjs | 1 + config/knip.json | 1 + electron.vite.config.ts | 3 + src/main/rate-limits/cursor-auth.test.ts | 4 +- src/main/rate-limits/cursor-auth.ts | 5 +- .../cursor-desktop-profile-worker-entry.ts | 19 +++ .../cursor-desktop-profile-worker.test.ts | 115 ++++++++++++++++++ .../cursor-desktop-profile-worker.ts | 106 ++++++++++++++++ 9 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/main/rate-limits/cursor-desktop-profile-worker-entry.ts create mode 100644 src/main/rate-limits/cursor-desktop-profile-worker.test.ts create mode 100644 src/main/rate-limits/cursor-desktop-profile-worker.ts diff --git a/config/build-plugins/plain-node-entry-guard.ts b/config/build-plugins/plain-node-entry-guard.ts index e0765a4873b..31340869e22 100644 --- a/config/build-plugins/plain-node-entry-guard.ts +++ b/config/build-plugins/plain-node-entry-guard.ts @@ -55,6 +55,7 @@ const PLAIN_NODE_ENTRY_NAMES = [ const WORKER_THREAD_ENTRY_NAMES = [ 'stt-worker', 'warp-theme-parser-worker', + 'cursor-desktop-profile-worker-entry', 'session-scanner-opencode-sqlite-worker-entry', 'session-scanner-worker-entry', 'main-thread-hang-watchdog-entry', diff --git a/config/electron-builder.config.cjs b/config/electron-builder.config.cjs index 86bf988bff9..20c3334bbdb 100644 --- a/config/electron-builder.config.cjs +++ b/config/electron-builder.config.cjs @@ -310,6 +310,7 @@ module.exports = { 'out/main/daemon-entry.js', 'out/main/session-scanner-service-entry.js', 'out/main/wsl-transcript-fs-process-entry.js', + 'out/main/cursor-desktop-profile-worker-entry.js', 'out/main/session-scanner-opencode-sqlite-worker-entry.js', 'out/main/plugin-host-entry.js', 'out/main/computer-sidecar.js', diff --git a/config/knip.json b/config/knip.json index 9af0b8d1a74..92c55c8ac7d 100644 --- a/config/knip.json +++ b/config/knip.json @@ -9,6 +9,7 @@ "src/main/computer/sidecar-entry.ts", "src/main/speech/stt-worker.ts", "src/main/warp-themes/warp-theme-parser-worker.ts", + "src/main/rate-limits/cursor-desktop-profile-worker-entry.ts", "src/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.ts", "src/main/ai-vault/session-scanner-worker-entry.ts", "src/main/ports/port-scan-command-worker-entry.ts", diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 221ac77b69f..d1b4e2d7b97 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -233,6 +233,9 @@ export const electronViteConfig: UserConfig = { 'daemon-entry': resolve('src/main/daemon/daemon-entry.ts'), 'plugin-host-entry': resolve('src/main/plugins/plugin-host-entry.ts'), 'computer-sidecar': resolve('src/main/computer/sidecar-entry.ts'), + 'cursor-desktop-profile-worker-entry': resolve( + 'src/main/rate-limits/cursor-desktop-profile-worker-entry.ts' + ), 'stt-worker': resolve('src/main/speech/stt-worker.ts'), 'warp-theme-parser-worker': resolve('src/main/warp-themes/warp-theme-parser-worker.ts'), 'session-scanner-opencode-sqlite-worker-entry': resolve( diff --git a/src/main/rate-limits/cursor-auth.test.ts b/src/main/rate-limits/cursor-auth.test.ts index 8280bf984cd..15566c32963 100644 --- a/src/main/rate-limits/cursor-auth.test.ts +++ b/src/main/rate-limits/cursor-auth.test.ts @@ -35,8 +35,8 @@ vi.mock('node:fs', () => ({ } })) -vi.mock('./cursor-desktop-state-db', () => ({ - readCursorDesktopProfile: () => desktopState.result +vi.mock('./cursor-desktop-profile-worker', () => ({ + readCursorDesktopProfileViaWorker: () => desktopState.result })) import { readCursorAuthSession, readCursorCliIdentity } from './cursor-auth' diff --git a/src/main/rate-limits/cursor-auth.ts b/src/main/rate-limits/cursor-auth.ts index 8ef86f0ba73..8288444b4f3 100644 --- a/src/main/rate-limits/cursor-auth.ts +++ b/src/main/rate-limits/cursor-auth.ts @@ -7,7 +7,8 @@ import { getCursorDesktopStateDbPath, type CursorAuthSource } from './cursor-auth-paths' -import { readCursorDesktopProfile, type CursorDesktopProfile } from './cursor-desktop-state-db' +import type { CursorDesktopProfile } from './cursor-desktop-state-db' +import { readCursorDesktopProfileViaWorker } from './cursor-desktop-profile-worker' import { isCursorSessionTokenExpired, parseCursorSessionToken, @@ -190,7 +191,7 @@ export async function readCursorAuthSession( // Why not pushed to `errors`: the IDE holds a lock on state.vscdb while it runs, // so a busy open is transient. Reporting it would pin an alert-triangle bar on // every Cursor IDE user who never set Cursor up in Orca. - const desktopRead = readCursorDesktopProfile(desktopDbPath) + const desktopRead = await readCursorDesktopProfileViaWorker(desktopDbPath) if (desktopRead.status === 'ok' && desktopRead.profile.accessToken) { const session = takeLive( sessionFrom(desktopRead.profile.accessToken, 'desktop', desktopIdentity(desktopRead.profile)) diff --git a/src/main/rate-limits/cursor-desktop-profile-worker-entry.ts b/src/main/rate-limits/cursor-desktop-profile-worker-entry.ts new file mode 100644 index 00000000000..f1a02157e1f --- /dev/null +++ b/src/main/rate-limits/cursor-desktop-profile-worker-entry.ts @@ -0,0 +1,19 @@ +import { parentPort } from 'node:worker_threads' +import { readCursorDesktopProfile } from './cursor-desktop-state-db' +import type { + CursorDesktopProfileRequest, + CursorDesktopProfileResponse +} from './cursor-desktop-profile-worker' + +const port = parentPort +if (!port) { + throw new Error('Cursor desktop login worker requires a parent port') +} + +port.on('message', ({ id, dbPath }: CursorDesktopProfileRequest) => { + const response: CursorDesktopProfileResponse = { + id, + result: readCursorDesktopProfile(dbPath) + } + port.postMessage(response) +}) diff --git a/src/main/rate-limits/cursor-desktop-profile-worker.test.ts b/src/main/rate-limits/cursor-desktop-profile-worker.test.ts new file mode 100644 index 00000000000..3da54b8fa34 --- /dev/null +++ b/src/main/rate-limits/cursor-desktop-profile-worker.test.ts @@ -0,0 +1,115 @@ +import { Worker } from 'node:worker_threads' +import { EventEmitter } from 'node:events' +import { afterEach, describe, expect, it } from 'vitest' +import { CursorDesktopProfileWorker } from './cursor-desktop-profile-worker' + +const clients: CursorDesktopProfileWorker[] = [] +afterEach(() => clients.splice(0).forEach((client) => client.dispose())) + +describe('Cursor desktop login worker', () => { + it('keeps the caller responsive during a slow read and coalesces concurrent probes', async () => { + let spawns = 0 + const client = new CursorDesktopProfileWorker(() => { + spawns++ + return new Worker( + `const { parentPort } = require('node:worker_threads'); + parentPort.on('message', ({id}) => { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250); + parentPort.postMessage({id, result: {status: 'missing'}}); + });`, + { eval: true } + ) + }) + clients.push(client) + const pending = client.read('/synthetic/state.vscdb') + expect(client.read('/synthetic/state.vscdb')).toBe(pending) + let completed = false + void pending.then(() => { + completed = true + }) + await new Promise((resolve) => setTimeout(resolve, 30)) + expect(completed).toBe(false) + expect(await pending).toEqual({ status: 'missing' }) + expect(spawns).toBe(1) + }) + + it('fails closed on unavailable workers without exposing filesystem details', async () => { + const client = new CursorDesktopProfileWorker(() => { + throw new Error('/private/account/path') + }) + clients.push(client) + expect(await client.read('/private/account/state.vscdb')).toEqual({ + status: 'error', + error: 'Unable to read the Cursor desktop login' + }) + }) + + it('terminates a stalled worker and allows a subsequent read to recover', async () => { + let spawns = 0 + const workers: Worker[] = [] + const client = new CursorDesktopProfileWorker(() => { + spawns++ + const worker = new Worker( + spawns === 1 + ? `setInterval(() => {}, 1000)` + : `const {parentPort} = require('node:worker_threads'); + parentPort.on('message', ({id}) => parentPort.postMessage({id, result: {status: 'missing'}}));`, + { eval: true } + ) + workers.push(worker) + return worker + }, 200) + clients.push(client) + expect((await client.read('/synthetic/state.vscdb')).status).toBe('error') + await workers[0]?.terminate() + expect(await client.read('/synthetic/state.vscdb')).toEqual({ + status: 'missing' + }) + expect(spawns).toBe(2) + }) + + it('refuses new native workers until the retired worker positively terminates', async () => { + let spawns = 0 + let finishRetirement: ((code: number) => void) | undefined + const retirement = new Promise((resolve) => { + finishRetirement = resolve + }) + const events = new EventEmitter() + const client = new CursorDesktopProfileWorker(() => { + spawns++ + return { + on: (...args) => events.on(...args), + off: (...args) => events.off(...args), + removeAllListeners: () => events.removeAllListeners(), + unref: () => undefined, + terminate: () => retirement, + postMessage: (request) => { + if (spawns > 1) { + queueMicrotask(() => + events.emit('message', { id: request.id, result: { status: 'missing' } }) + ) + } + } + } + }, 20) + clients.push(client) + expect((await client.read('/synthetic/state.vscdb')).status).toBe('error') + for (let i = 0; i < 3; i++) { + expect((await client.read('/synthetic/state.vscdb')).status).toBe('error') + } + expect(spawns).toBe(1) + finishRetirement?.(1) + await retirement + expect(await client.read('/synthetic/state.vscdb')).toEqual({ status: 'missing' }) + expect(spawns).toBe(2) + }) + + it('settles pending reads when disposed', async () => { + const client = new CursorDesktopProfileWorker( + () => new Worker('setInterval(() => {}, 1000)', { eval: true }) + ) + const pending = client.read('/synthetic/state.vscdb') + client.dispose() + expect((await pending).status).toBe('error') + }) +}) diff --git a/src/main/rate-limits/cursor-desktop-profile-worker.ts b/src/main/rate-limits/cursor-desktop-profile-worker.ts new file mode 100644 index 00000000000..754cd799853 --- /dev/null +++ b/src/main/rate-limits/cursor-desktop-profile-worker.ts @@ -0,0 +1,106 @@ +import { existsSync } from 'node:fs' +import { join } from 'node:path' +import { Worker } from 'node:worker_threads' +import type { WorkerRequestTransport, WorkerThreadFactory } from '../lazy-worker-thread-host' +import { WorkerThreadRequestQueue } from '../worker-thread-request-queue' +import type { CursorDesktopProfileReadResult } from './cursor-desktop-state-db' + +export type CursorDesktopProfileRequest = { id: number; dbPath: string } +export type CursorDesktopProfileResponse = { + id: number + result: CursorDesktopProfileReadResult +} + +const ENTRY = 'cursor-desktop-profile-worker-entry.js' +const READ_ERROR = 'Unable to read the Cursor desktop login' + +export function resolveCursorDesktopProfileWorkerPath(runtimeDir = __dirname): string { + for (const candidate of [join(runtimeDir, ENTRY), join(runtimeDir, '..', ENTRY)]) { + if (existsSync(candidate)) { + return candidate + } + } + throw new Error('Cursor desktop login worker is unavailable') +} + +export class CursorDesktopProfileWorker { + private readonly queue: WorkerThreadRequestQueue< + CursorDesktopProfileRequest, + CursorDesktopProfileResponse + > + private readonly inflight = new Map>() + private retiring = false + + constructor( + factory: WorkerThreadFactory, + private readonly timeoutMs = 10_000 + ) { + this.queue = new WorkerThreadRequestQueue({ + factory: () => this.createWorker(factory), + idleTeardownMs: 30_000, + maxConsecutiveDeaths: 2, + queueCap: { maxQueuedCalls: 8, describeFull: () => READ_ERROR }, + createUnavailableError: () => new Error(READ_ERROR), + describeTimeout: () => READ_ERROR, + describeExit: () => READ_ERROR, + describeCrashLoop: () => READ_ERROR, + onUnavailable: () => undefined + }) + } + + read(dbPath: string): Promise { + const pending = this.inflight.get(dbPath) + if (pending) { + return pending + } + const read = this.queue + .dispatch((id) => ({ id, dbPath }), this.timeoutMs) + .then(({ result }) => result) + .catch((): CursorDesktopProfileReadResult => ({ + status: 'error', + error: READ_ERROR + })) + .finally(() => this.inflight.delete(dbPath)) + this.inflight.set(dbPath, read) + return read + } + + dispose(): void { + this.queue.dispose() + this.inflight.clear() + } + + private createWorker(factory: WorkerThreadFactory): WorkerRequestTransport { + if (this.retiring) { + throw new Error(READ_ERROR) + } + const worker = factory() + return { + on: (...args) => worker.on(...args), + off: (...args) => worker.off(...args), + removeAllListeners: () => worker.removeAllListeners(), + unref: () => worker.unref(), + postMessage: (...args) => worker.postMessage(...args), + terminate: () => { + // Native SQLite work can delay termination; never overlap retired workers. + this.retiring = true + return worker.terminate().then((code) => { + this.retiring = false + return code + }) + } + } + } +} + +let client: CursorDesktopProfileWorker | undefined + +export function readCursorDesktopProfileViaWorker( + dbPath: string +): Promise { + // WAL recovery must never fall back to the Electron main thread. + client ??= new CursorDesktopProfileWorker( + () => new Worker(resolveCursorDesktopProfileWorkerPath()) + ) + return client.read(dbPath) +} From d627b13cdd69b208901a9b2b984518d136fa0b9d Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:48:35 -0700 Subject: [PATCH 05/25] fix(cursor): distinguish usage failures from expired login (#24575) --- src/main/rate-limits/cursor-fetcher.test.ts | 22 ++++++++++++++------- src/main/rate-limits/cursor-fetcher.ts | 6 ++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/rate-limits/cursor-fetcher.test.ts b/src/main/rate-limits/cursor-fetcher.test.ts index b334713a9af..bb07fb90b05 100644 --- a/src/main/rate-limits/cursor-fetcher.test.ts +++ b/src/main/rate-limits/cursor-fetcher.test.ts @@ -119,14 +119,22 @@ describe('fetchCursorRateLimits', () => { expect(limited.usageMetadata?.retryAtMs).toBeGreaterThan(Date.now()) }) - it('treats a redirect to the login page as an expired sign-in', async () => { - // Why: the dashboard bounces an unusable session to /login. With - // redirect:'error' that surfaced as a generic network failure, hiding the - // one message that tells the user what to do. - netFetchMock.mockResolvedValueOnce(jsonResponse({}, 302, { location: '/login' })) + it.each([403, 302, 304])('reports HTTP %i without claiming the login expired', async (status) => { + netFetchMock.mockResolvedValueOnce(jsonResponse({}, status, { location: '/login' })) const limits = await fetchCursorRateLimits({ authReadResult: session() }) - expect(limits.usageMetadata?.failureKind).toBe('stale-token') - expect(limits.error).toContain('cursor-agent login') + expect(limits.usageMetadata?.failureKind).toBe('server') + expect(limits.error).toBe(`Cursor usage request failed (HTTP ${status})`) + expect(limits.usageMetadata?.authProvenance).toBeTruthy() + expect(netFetchMock.mock.calls[0]?.[1]?.redirect).toBe('manual') + }) + + it('preserves a forbidden response from the legacy usage endpoint', async () => { + netFetchMock + .mockResolvedValueOnce(jsonResponse({ membershipType: 'free' })) + .mockResolvedValueOnce(jsonResponse({}, 403)) + const limits = await fetchCursorRateLimits({ authReadResult: session() }) + expect(limits.error).toBe('Cursor usage request failed (HTTP 403)') + expect(limits.usageMetadata?.failureKind).toBe('server') }) it('names the account on failures too, so an account switch can clear stale figures', async () => { diff --git a/src/main/rate-limits/cursor-fetcher.ts b/src/main/rate-limits/cursor-fetcher.ts index d188127a19a..82bbf1c0e48 100644 --- a/src/main/rate-limits/cursor-fetcher.ts +++ b/src/main/rate-limits/cursor-fetcher.ts @@ -82,14 +82,12 @@ async function fetchDashboardJson( ? AbortSignal.any([signal, AbortSignal.timeout(API_TIMEOUT_MS)]) : AbortSignal.timeout(API_TIMEOUT_MS) const res = await net.fetch(url, { - // Why manual: the dashboard bounces an unusable session to /login, and - // 'error' would surface that as a generic network failure instead of the - // actionable sign-in message below. + // Keep dashboard redirects visible without forwarding session credentials. redirect: 'manual', headers: requestHeaders(session), signal: requestSignal }) - if (res.status === 401 || res.status === 403 || (res.status >= 300 && res.status < 400)) { + if (res.status === 401) { return { kind: 'result', result: result('error', EXPIRED_MESSAGE, { From 4e46969b7c17b3fb5988751797bdf3dd9f2f9333 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:50:43 -0700 Subject: [PATCH 06/25] fix(cursor): preserve UTF-8 in Windows hook transport (#24585) * fix(cursor): preserve Windows hook input across profile paths Adapt the reviewed direct-command approach from PR #24381, and set UTF-8 input/output encoding for profiles requiring PowerShell. Co-authored-by: Vladimir Kurgansky * fix(cursor): preserve missing-script permission replies Keep the established encoded launcher for every event and explicitly use UTF-8 input/output. Preserve original missing-script acceptance and verify ordinary/spaced profiles rather than adding shell-specific direct guards. --------- Co-authored-by: Vladimir Kurgansky --- src/main/cursor/hook-script.ts | 22 +++++---- src/main/cursor/hook-service.test.ts | 72 ++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/main/cursor/hook-script.ts b/src/main/cursor/hook-script.ts index 739026aa6a8..bbc40bf2587 100644 --- a/src/main/cursor/hook-script.ts +++ b/src/main/cursor/hook-script.ts @@ -1,7 +1,8 @@ import { buildWindowsAgentHookPostCommand, wrapPosixHookCommand, - wrapWindowsHookCommand + buildWindowsHookPowerShellCommand, + wrapWindowsPowerShellEncodedCommand } from '../agent-hooks/installer-utils' import { buildPosixHookPayloadCapture, @@ -27,14 +28,19 @@ export function getPosixManagedCommand(scriptPath: string, eventName: CursorEven } export function getManagedCommand(scriptPath: string, eventName: CursorEvent): string { + if (process.platform !== 'win32') { + return getPosixManagedCommand(scriptPath, eventName) + } const response = getCursorHookResponse(eventName) - return process.platform === 'win32' - ? wrapWindowsHookCommand( - scriptPath, - { [CURSOR_HOOK_RESPONSE_ENV]: response }, - { fallbackStdout: response } - ) - : getPosixManagedCommand(scriptPath, eventName) + const command = buildWindowsHookPowerShellCommand( + scriptPath, + { [CURSOR_HOOK_RESPONSE_ENV]: response }, + { fallbackStdout: response } + ) + // PowerShell 5.1 defaults to the ANSI code page when translating hook stdin. + return wrapWindowsPowerShellEncodedCommand( + `[Console]::InputEncoding = New-Object System.Text.UTF8Encoding; $OutputEncoding = [Console]::InputEncoding; ${command}` + ) } export function getManagedScript(target: 'local' | 'posix' = 'local'): string { diff --git a/src/main/cursor/hook-service.test.ts b/src/main/cursor/hook-service.test.ts index 3f93e91f7fe..e1c335e7bb9 100644 --- a/src/main/cursor/hook-service.test.ts +++ b/src/main/cursor/hook-service.test.ts @@ -4,6 +4,8 @@ import { removeTreeSync } from '../../shared/windows-transient-lock-removal' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { spawnSync } from 'node:child_process' +import { createServer, type Server } from 'node:http' +import { runProcess } from '../../shared/child-process/run-process' import { findGitBash } from '../agent-hooks/windows-git-bash-path.test-fixture' const { homedirMock } = vi.hoisted(() => ({ @@ -241,12 +243,15 @@ describe('CursorHookService', () => { HOOK_CASE_TIMEOUT_MS ) - it( - 'emits protocol-valid JSON when the managed Cursor script is missing (#15462)', - () => { + it.each(['ordinary', 'spaced'] as const)( + 'emits protocol-valid JSON when the script is missing in a %s profile (#15462)', + (profileKind) => { + const installHome = profileKind === 'spaced' ? join(homeDir, 'profile with spaces') : homeDir + mkdirSync(installHome, { recursive: true }) + homedirMock.mockReturnValue(installHome) expect(new CursorHookService().install().state).toBe('installed') - const config = readInstalledCursorHooks(homeDir) - unlinkSync(join(homeDir, '.orca', 'agent-hooks', CURSOR_SCRIPT_FILE_NAME)) + const config = readInstalledCursorHooks(installHome) + unlinkSync(join(installHome, '.orca', 'agent-hooks', CURSOR_SCRIPT_FILE_NAME)) for (const eventName of CURSOR_EVENTS) { const command = requireRegisteredCommand(config, eventName) @@ -287,6 +292,63 @@ describe('CursorHookService', () => { HOOK_CASE_TIMEOUT_MS ) + it.skipIf(process.platform !== 'win32').each(['ordinary', 'spaced'] as const)( + 'posts UTF-8 stdin unchanged for a %s profile path', + async (profileKind) => { + const installHome = profileKind === 'spaced' ? join(homeDir, 'profile with spaces') : homeDir + mkdirSync(installHome, { recursive: true }) + homedirMock.mockReturnValue(installHome) + expect(new CursorHookService().install().state).toBe('installed') + const command = requireRegisteredCommand( + readInstalledCursorHooks(installHome), + 'afterAgentResponse' + ) + expect(command).toMatch(WINDOWS_POWERSHELL_LAUNCHER) + const payload = JSON.stringify({ text: 'Сердце, почта в ЛК — 日本語 中文 한국어 😀 café' }) + const posts: string[] = [] + const server: Server = createServer((req, res) => { + const chunks: Buffer[] = [] + req.on('data', (chunk: Buffer) => chunks.push(chunk)) + req.on('end', () => { + const form = new URLSearchParams(Buffer.concat(chunks).toString('utf8')) + const posted = form.get('payload') + if (posted !== null) { + posts.push(posted) + } + res.writeHead(204) + res.end() + }) + }) + const port = await new Promise((resolve) => { + server.listen(0, '127.0.0.1', () => { + const address = server.address() + resolve(typeof address === 'object' && address ? address.port : 0) + }) + }) + try { + const result = await runProcess({ + program: 'cmd.exe', + args: ['/d', '/c', command], + input: payload, + timeoutMs: HOOK_RUN_TIMEOUT_MS, + env: { + ...process.env, + ORCA_AGENT_HOOK_ENDPOINT: '', + ORCA_AGENT_HOOK_PORT: String(port), + ORCA_AGENT_HOOK_TOKEN: 'token', + ORCA_PANE_KEY: 'tab:leaf' + } + }) + expect(result.code).toBe(0) + expect(result.timedOut).toBe(false) + expect(JSON.parse(result.stdout)).toEqual({}) + expect(posts).toEqual([payload]) + } finally { + await new Promise((resolve) => server.close(() => resolve())) + } + } + ) + it.skipIf(process.platform !== 'win32')( 'emits parseable JSON through cmd.exe and Git Bash (#14825/#15462)', () => { From 99b2628aef29ca578bc241815e774c86b9c2c718 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:12:10 -0700 Subject: [PATCH 07/25] test: update sidebar setup and remove obsolete permission sentinel (#24734) --- .../e2e/issue-12907-pi-eof-agent-row.spec.ts | 2 +- tests/e2e/onboarding.spec.ts | 30 ------------------- tests/e2e/sidebar-agent-row-identity.spec.ts | 2 +- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/tests/e2e/issue-12907-pi-eof-agent-row.spec.ts b/tests/e2e/issue-12907-pi-eof-agent-row.spec.ts index a85e41c0708..5f9c427bce8 100644 --- a/tests/e2e/issue-12907-pi-eof-agent-row.spec.ts +++ b/tests/e2e/issue-12907-pi-eof-agent-row.spec.ts @@ -27,7 +27,7 @@ test('Pi EOF removes the completed agent row from the live Electron sidebar (#12 } state.setAgentActivityDisplayMode('full') if (!state.worktreeCardProperties.includes('inline-agents')) { - state.toggleWorktreeCardProperty('inline-agents') + state.setWorktreeCardProperties([...state.worktreeCardProperties, 'inline-agents']) } }) diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index 94dba0c6732..a5a6557b4b9 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -548,36 +548,6 @@ test.describe('Onboarding flow', () => { expect((await getOnboardingState(orcaPage)).closedAt).not.toBeNull() }) - test('Skip from notifications does not request permission', async ({ orcaPage }) => { - await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({ - timeout: 15_000 - }) - await continueOnboarding(orcaPage) - await expect(orcaPage.getByRole('heading', { name: /Make it feel like home/i })).toBeVisible() - await continueFromThemeToNotifications(orcaPage) - - await orcaPage.evaluate(() => { - localStorage.removeItem('orca.e2e.notificationPermissionRequested') - window.api.notifications.requestPermission = async () => { - localStorage.setItem('orca.e2e.notificationPermissionRequested', '1') - return { supported: true, platform: 'darwin', requested: true } - } - }) - await expectOnboardingNotificationSound(orcaPage, /System Default/i) - - await expect(onboardingFooterButton(orcaPage, SKIP_TO_PROJECT_SETUP_BUTTON)).toHaveCount(0) - await continueOnboarding(orcaPage) - - await expectAddProjectDialog(orcaPage) - await expect - .poll( - async () => - orcaPage.evaluate(() => localStorage.getItem('orca.e2e.notificationPermissionRequested')), - { timeout: 5_000 } - ) - .toBeNull() - }) - test('selected agent button reports aria-pressed=true', async ({ orcaPage }) => { await expect(orcaPage.getByRole('heading', { name: /Pick your default agent/i })).toBeVisible({ timeout: 15_000 diff --git a/tests/e2e/sidebar-agent-row-identity.spec.ts b/tests/e2e/sidebar-agent-row-identity.spec.ts index a221c7bf156..7eb48348ff3 100644 --- a/tests/e2e/sidebar-agent-row-identity.spec.ts +++ b/tests/e2e/sidebar-agent-row-identity.spec.ts @@ -50,7 +50,7 @@ async function useFullAgentActivityRows(page: Page): Promise { // reads off the rendered sidebar instead of a collapsed summary pill. state.setAgentActivityDisplayMode('full') if (!state.worktreeCardProperties.includes('inline-agents')) { - state.toggleWorktreeCardProperty('inline-agents') + state.setWorktreeCardProperties([...state.worktreeCardProperties, 'inline-agents']) } }) } From cd8d03bc06398b856f6db734aef11df4e2afe15a Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:26:00 -0700 Subject: [PATCH 08/25] fix(dsh): recognize 0.2 profiles and open workspace composer (#24589) --- docs/reference/dsh-harness-integration.md | 44 +++++++++++++++++++ src/shared/dsh-launch-command.test.ts | 24 ++++++++++ src/shared/dsh-launch-command.ts | 45 +++++++------------ src/shared/dsh-real-cli.test.ts | 53 +++++++++++++++++++++++ src/shared/dsh-startup.test.ts | 21 +++++++++ src/shared/tui-agent-config.ts | 13 ++---- 6 files changed, 160 insertions(+), 40 deletions(-) create mode 100644 docs/reference/dsh-harness-integration.md create mode 100644 src/shared/dsh-real-cli.test.ts create mode 100644 src/shared/dsh-startup.test.ts diff --git a/docs/reference/dsh-harness-integration.md b/docs/reference/dsh-harness-integration.md new file mode 100644 index 00000000000..9a850e06900 --- /dev/null +++ b/docs/reference/dsh-harness-integration.md @@ -0,0 +1,44 @@ +# DeepSeek Harness integration + +Orca detects the community `@deepseek-harness-tui/dsh-tui` launcher (`dsh-tui`, alias +`dst`) and requires the official `@deepseek-ai/dsh` executable too. The launcher +boots the `dsh-tui` profile; Orca passes `.` to select the current workspace and +reach its composer on the first launch. The official Harness does not bundle this community TUI. +DSH Console and DeepSeek Build are separate products and are not interchangeable +with this launch contract. + +The official DSH 0.2 CLI accepts both `dsh --profile headless` and `dsh headless`. +Orca excludes the known `web`, `headless`, `sdk`, `sdk-minimal`, `acp`, and `desktop` +profiles from interactive process recognition, along with plugin management and +configuration dumps. Custom profile names remain eligible because profiles are +user configurable. Only launcher arguments are inspected; app prompts, resume IDs, +and patch filenames cannot change the selected profile's identity. + +Status hooks use the official `@deepseek-ai/dsh-hooks-claude-code` plugin, installed +as an owned block in `$DSH_HOME/cordis.patch.yml`. User entries outside the block are +preserved. Local installation respects `DSH_HOME`; the existing SSH installer uses +the execution host's default `~/.dsh` because SFTP cannot read its environment. +Hooks report session start, prompt submission, tool start/end, and stopping through +Orca's host status store. Approval has no dedicated hook; it is not inferred from +an uncaptured screen. Subagent lifecycle events are ignored for parent-pane status. + +DSH 0.2 still emits an empty `transcript_path` in Claude-compatible hooks. Its +session persistence defaults to compressed JSONL under `$DSH_HOME/sessions`. +Orca can resume a hook-associated session through `dsh-tui --resume `, but +currently does not discover DSH logs in Agent Session History. Resume support alone +does not establish transcript-history support. + +## Reproduce the official launcher check + +Install `@deepseek-ai/dsh@0.2.0-rc.2` into a disposable prefix, then run: + +```sh +ORCA_BACKGROUND_LAUNCH=1 ORCA_REAL_DSH_CLI=/path/to/prefix/node_modules/.bin/dsh \ + pnpm test src/shared/dsh-real-cli.test.ts +``` + +The opt-in test checks published version, composed profile configurations, and +headless help in an isolated home and working folder without a model request. +Interactive readiness is separately pinned to the captured community TUI transcript +in `src/main/runtime/__fixtures__/dsh-tui-ready-no-key.txt`; that older capture is +not proof of current TUI compatibility or paid generation. diff --git a/src/shared/dsh-launch-command.test.ts b/src/shared/dsh-launch-command.test.ts index 6585318cc98..f8bf718a211 100644 --- a/src/shared/dsh-launch-command.test.ts +++ b/src/shared/dsh-launch-command.test.ts @@ -9,6 +9,14 @@ function tokens(commandLine: string): string[] { describe('isDshNonInteractiveCommand', () => { it.each([ 'dsh web', + 'dsh headless "run the tests"', + 'dsh sdk', + 'dsh sdk-minimal', + 'dsh acp', + 'dsh desktop', + 'dsh dsh-tui --dump-config', + 'dsh dsh-tui --dump-default-config', + 'dsh dsh-tui --dump-config-schema', 'dsh --profile web', 'dsh --profile=web --port 8080', 'dsh --profile headless "run the tests"', @@ -23,6 +31,14 @@ describe('isDshNonInteractiveCommand', () => { it.each([ 'dsh --profile dsh-tui', + 'dsh dsh-tui', + 'dsh tui --resume web', + 'dsh tui --patch web --resume sdk', + 'dsh tui --patch=web --resume sdk', + 'dsh --profile tui --patch headless --resume sdk', + 'dsh tui --from-default-profile web --resume sdk', + 'dsh tui --resume session --dump-config', + 'dsh --profile tui --resume session --dump-config', 'dsh --profile=dsh-tui', 'dsh --profile dsh-tui --resume 9478e2d8-29bc-4009-ab32-657efa2bd763', 'dsh-tui', @@ -66,6 +82,14 @@ describe('dsh foreground process recognition', () => { it.each([ 'dsh web', + 'dsh headless "summarize the diff"', + 'dsh sdk', + 'dsh sdk-minimal', + 'dsh acp', + 'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js headless "go"', + 'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js sdk', + 'node --no-warnings /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js sdk', + 'C:\\Tools\\dsh.cmd headless go', 'dsh --profile headless "summarize the diff"', 'node /usr/local/lib/node_modules/@deepseek-ai/dsh/lib/bin.js --profile acp' ])('does not claim %s as an interactive agent pane', (commandLine) => { diff --git a/src/shared/dsh-launch-command.ts b/src/shared/dsh-launch-command.ts index 60100a9823b..4c44fbf9c4d 100644 --- a/src/shared/dsh-launch-command.ts +++ b/src/shared/dsh-launch-command.ts @@ -1,11 +1,6 @@ -// DeepSeek Harness ships one binary, `dsh`, and boots a *profile* with it. Only the -// `dsh-tui` profile paints an interactive composer; `web` serves HTTP, `headless` answers -// one task and exits, `sdk`/`sdk-minimal`/`acp` speak JSON-RPC on stdio, and `plugin` is -// package management. All five are `dsh` in the process table, so foreground recognition -// has to read the profile to tell an agent pane from a server or a one-shot. -// -// `dsh-tui` (and its `dst` alias) forward straight to `dsh --profile dsh-tui`, so they are -// always interactive and never reach this matcher. +import { findInterpreterEntrypointToken } from './agent-command-line-entrypoint' + +// DSH 0.2 accepts positional profiles; community dsh-tui/dst choose their own TUI profile. /** Profiles that boot something other than the interactive terminal UI. */ const NON_INTERACTIVE_PROFILES = new Set([ @@ -17,9 +12,8 @@ const NON_INTERACTIVE_PROFILES = new Set([ 'desktop' ]) -/** Bare-word subcommands the launcher accepts: `plugin` boots no profile at all, and `web` - * is the documented alias for `--profile web`. Neither hosts an interactive pane. */ -const SUBCOMMANDS = new Set(['plugin', 'web']) +/** Plugin management never boots an interactive pane. */ +const SUBCOMMANDS = new Set(['plugin']) /** Launcher flags that print a composed config and exit. */ const DUMP_FLAGS = new Set(['--dump-config', '--dump-default-config', '--dump-config-schema']) @@ -69,30 +63,21 @@ function isLauncherToken(token: string): boolean { * and a prompt or session id is free text that must never be read as a launcher flag. */ export function isDshNonInteractiveCommand(tokens: readonly string[]): boolean { - // Why first: `dsh-tui`/`dst` have already chosen the interactive profile, and everything - // after them is the TERMINAL APP's argv — a `--resume` id or a workspace target. A - // workspace folder named `web` or `plugin` is an ordinary directory name, and reading it - // as a `dsh` subcommand would mark a live agent pane non-interactive, costing it status - // hooks and prompt delivery. Index 1 as well as 0 because a node shim puts the launcher - // script path there. - if ( - TUI_LAUNCHER_NAMES.has(programBasename(tokens[0])) || - TUI_LAUNCHER_NAMES.has(programBasename(tokens[1])) - ) { + const firstProgram = programBasename(tokens[0]) + const entrypoint = findInterpreterEntrypointToken([...tokens], firstProgram) + const programIndex = entrypoint === null ? 0 : tokens.indexOf(entrypoint) + if (TUI_LAUNCHER_NAMES.has(programBasename(tokens[programIndex]))) { return false } + const indexOfArgs = programIndex + 1 + let index = indexOfArgs let profile: string | null = null - let index = 1 - // Skip the leading non-flag tokens: an interpreter invocation puts the script path here. - while (index < tokens.length && !isLauncherToken(tokens[index])) { - const token = tokens[index] - if (SUBCOMMANDS.has(token)) { + const first = tokens[index] + if (first !== undefined && !first.startsWith('-')) { + if (SUBCOMMANDS.has(first)) { return true } - if (!token.startsWith('-') && index > 1) { - // A bare word that is neither a subcommand nor a flag: the app's arguments start here. - return false - } + profile = first index += 1 } for (; index < tokens.length; index += 1) { diff --git a/src/shared/dsh-real-cli.test.ts b/src/shared/dsh-real-cli.test.ts new file mode 100644 index 00000000000..6422b9884fa --- /dev/null +++ b/src/shared/dsh-real-cli.test.ts @@ -0,0 +1,53 @@ +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' +import { runProcess } from './child-process/run-process' +import { recognizeAgentProcessFromCommandLine } from './agent-process-recognition' + +const binary = process.env.ORCA_REAL_DSH_CLI + +// No model call: published profile configuration and help establish the launcher contract. +describe.skipIf(!binary)('published DSH 0.2 CLI', () => { + it('keeps positional and explicit headless profiles outside interactive recognition', async () => { + if (!binary) { + throw new Error('Set ORCA_REAL_DSH_CLI to the official dsh executable') + } + const root = await mkdtemp(join(tmpdir(), 'orca-dsh-cli-')) + const env = { ...process.env, DSH_HOME: root, DEEPSEEK_API_KEY: '' } + try { + const version = await runProcess({ program: binary, args: ['--version'], cwd: root, env }) + expect(version.code).toBe(0) + expect(version.stdout.trim()).toMatch(/^0\.2\./) + for (const profile of ['headless', 'sdk', 'sdk-minimal', 'acp']) { + for (const args of [[profile], ['--profile', profile]]) { + const result = await runProcess({ + program: binary, + args: [...args, '--dump-default-config'], + cwd: root, + env, + timeoutMs: 30_000 + }) + expect(result.code).toBe(0) + expect(result.stdout).toContain('@deepseek-ai/dsh-') + const command = `"${binary}" ${args.join(' ')}` + expect(recognizeAgentProcessFromCommandLine(command)).toBeNull() + expect( + recognizeAgentProcessFromCommandLine(command, { includeHeadlessOneShot: true })?.agent + ).toBe('dsh') + } + } + const help = await runProcess({ + program: binary, + args: ['headless', '--help'], + cwd: root, + env, + timeoutMs: 30_000 + }) + expect(help.code).toBe(0) + expect(help.stdout).toContain('Answer one task and exit') + } finally { + await rm(root, { recursive: true, force: true }) + } + }, 120_000) +}) diff --git a/src/shared/dsh-startup.test.ts b/src/shared/dsh-startup.test.ts new file mode 100644 index 00000000000..0a99d2cc67b --- /dev/null +++ b/src/shared/dsh-startup.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { buildAgentStartupPlan } from './tui-agent-startup' +import { TUI_AGENT_CONFIG } from './tui-agent-config' + +describe('DSH first launch', () => { + it.each(['darwin', 'linux', 'win32'] as const)( + 'selects the current workspace before pasting the task on %s', + (platform) => { + const plan = buildAgentStartupPlan({ + agent: 'dsh', + prompt: 'Review this folder', + cmdOverrides: {}, + platform + }) + expect(plan?.launchCommand).toContain('dsh-tui .') + expect(plan?.launchCommand).not.toContain('Review this folder') + expect(TUI_AGENT_CONFIG.dsh.promptInjectionMode).toBe('stdin-after-start') + expect(TUI_AGENT_CONFIG.dsh.draftPasteReadySignal).toBe('dsh-composer-prompt') + } + ) +}) diff --git a/src/shared/tui-agent-config.ts b/src/shared/tui-agent-config.ts index d9fdabb14c3..a7be259df2c 100644 --- a/src/shared/tui-agent-config.ts +++ b/src/shared/tui-agent-config.ts @@ -290,21 +290,14 @@ const TUI_AGENT_CONFIG_SOURCE: Record = { promptInjectionMode: 'stdin-after-start' }, dsh: { - // Why: DeepSeek Harness publishes one binary (`dsh`) that boots a profile, and only the - // `dsh-tui` profile paints a composer. `dsh-tui` (alias `dst`) is the launcher that - // selects it, so detect that and require `dsh` too — the launcher delegates to it and - // fails without it. detectCmd: 'dsh-tui', detectCmdAliases: ['dst'], detectRequiredCommands: ['dsh'], - // Why: the launcher re-execs `dsh --profile dsh-tui`, so the pane's foreground process - // is `dsh`, never `dsh-tui`. Readiness and follow-up delivery key off this name. + // A first bare launch opens the session browser instead of the composer. + launchCmd: 'dsh-tui .', expectedProcess: 'dsh', - // Why: the terminal app parses only `--resume`/`--continue` and a workspace target; it - // has no prompt flag, so the first prompt is pasted into the composer after startup. promptInjectionMode: 'stdin-after-start', - // Why: DSH-TUI animates a whale intro continuously behind its composer, so the default - // quiet window never settles (the grok failure mode). See dsh-tui-ready-no-key.txt. + // The whale intro keeps repainting behind the ready composer. draftPasteReadySignal: 'dsh-composer-prompt' }, zcode: { From adf447d958b2e7eb9c85b0e9b12f634683c4ecbf Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:35:01 -0700 Subject: [PATCH 09/25] test: classify acknowledged remount input as driving (#24750) --- tests/e2e/live-background-terminal-mount-authority.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e/live-background-terminal-mount-authority.spec.ts b/tests/e2e/live-background-terminal-mount-authority.spec.ts index 28ad815f0b2..39735b0c1e8 100644 --- a/tests/e2e/live-background-terminal-mount-authority.spec.ts +++ b/tests/e2e/live-background-terminal-mount-authority.spec.ts @@ -612,6 +612,9 @@ test('adopts runtime-owned agent and Setup PTYs on first mount', async ({ expect(agent).toBeTruthy() expect(setup).toBeTruthy() expect(canary).toBeTruthy() + if (!agent?.ptyId || !setup?.ptyId || !canary?.ptyId) { + throw new Error('Agent, Setup, and canary terminals must have live PTY identities') + } await expect.poll(readSpawnLedger).toHaveLength(1) await expect.poll(() => readJsonLines<{ pid: number }>(setupLedgerPath)).toHaveLength(1) await expect.poll(() => readJsonLines<{ pid: number }>(canaryLedgerPath)).toHaveLength(1) @@ -771,7 +774,7 @@ test('adopts runtime-owned agent and Setup PTYs on first mount', async ({ const remountAgentAcceptedMarker = `AGENT_ACCEPTED_${randomUUID()}` expect( await orcaPage.evaluate( - ({ marker, ptyId }) => window.api.pty.writeAccepted(ptyId, `${marker}\r`), + ({ marker, ptyId }) => window.api.pty.writeAccepted(ptyId, `${marker}\r`, 'driving'), { marker: remountAgentAcceptedMarker, ptyId: agent!.ptyId } ) ).toBe(true) From 302526411dd11fa46d6a6e49419fbd518d20cd70 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:52:50 -0700 Subject: [PATCH 10/25] ci: share bounded apt setup with the E2E native cache job (#24758) --- .github/workflows/e2e.yml | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 517c77c9ddd..2957c8eaa6e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -119,7 +119,22 @@ jobs: ref: ${{ inputs.ref || github.ref }} - name: Install native build tools - run: sudo apt-get update && sudo apt-get install -y build-essential python3 + env: + ORCA_E2E_APT_PACKAGES: build-essential python3 + run: &install_e2e_tools | + read -r -a packages <<< "$ORCA_E2E_APT_PACKAGES" + for source in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do + if [ -f "$source" ]; then + sudo sed -i 's|https*://azure\.archive\.ubuntu\.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' "$source" + fi + done + sudo tee /etc/apt/apt.conf.d/99-orca-e2e >/dev/null <<'APTCONF' + Acquire::http::Timeout "15"; + Acquire::https::Timeout "15"; + Acquire::Retries "1"; + APTCONF + timeout 120 sudo apt-get update + timeout 300 sudo apt-get install -y "${packages[@]}" - uses: ./.github/actions/install-node-dependencies with: @@ -178,19 +193,9 @@ jobs: - name: Install native build and headless UI tools # The Azure archive took 16 minutes for one font package; bound setup # separately so a slow mirror cannot consume the shard's test budget. - run: &install_e2e_tools | - for source in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do - if [ -f "$source" ]; then - sudo sed -i 's|https*://azure\.archive\.ubuntu\.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' "$source" - fi - done - sudo tee /etc/apt/apt.conf.d/99-orca-e2e >/dev/null <<'APTCONF' - Acquire::http::Timeout "15"; - Acquire::https::Timeout "15"; - Acquire::Retries "1"; - APTCONF - timeout 120 sudo apt-get update - timeout 300 sudo apt-get install -y build-essential fonts-noto-cjk openssh-client python3 ripgrep xvfb zsh openbox x11-utils + env: &e2e_tool_packages + ORCA_E2E_APT_PACKAGES: build-essential fonts-noto-cjk openssh-client python3 ripgrep xvfb zsh openbox x11-utils + run: *install_e2e_tools - uses: ./.github/actions/install-node-dependencies with: @@ -281,6 +286,7 @@ jobs: # unbounded inventory fallback; the paired fixture exercises that real boundary. # Why openssh-client: the Docker-SSH fixture shells out to ssh/ssh-keygen, and this # lane now receives those specs from pr.yml's SSH source mapping. + env: *e2e_tool_packages run: *install_e2e_tools - uses: ./.github/actions/install-node-dependencies @@ -417,6 +423,7 @@ jobs: ref: ${{ inputs.ref || github.ref }} - name: Install native build and headless UI tools + env: *e2e_tool_packages run: *install_e2e_tools - uses: ./.github/actions/install-node-dependencies From cb03a0545a0e3bb781c72c6a654a38cb207c9445 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:56:12 -0700 Subject: [PATCH 11/25] fix(opencode): keep absent session stores quiet (#24577) * fix(opencode): skip absent session stores without warning * fix(opencode): retain warnings for inaccessible session stores --------- Co-authored-by: drakeo338 --- .../server-opencode-binder.test.ts | 45 ++++++++++++++++++- src/main/opencode/opencode-session-binder.ts | 10 +++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/main/agent-hooks/server-opencode-binder.test.ts b/src/main/agent-hooks/server-opencode-binder.test.ts index eaa5dff073e..ab6ed0ad0ba 100644 --- a/src/main/agent-hooks/server-opencode-binder.test.ts +++ b/src/main/agent-hooks/server-opencode-binder.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, rmSync } from 'node:fs' +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -317,4 +317,47 @@ describe('listOpenCodeDbSessions', () => { it('returns [] for a missing database instead of throwing', () => { expect(listOpenCodeDbSessions(join(dir, 'absent.db'), { ms: 0, id: '' })).toEqual([]) }) + + it('stays quiet for a missing database and detects it once it appears', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + try { + const absent = join(dir, 'later.db') + expect(listOpenCodeDbSessions(absent, { ms: 0, id: '' })).toEqual([]) + expect(listOpenCodeDbSessions(absent, { ms: 0, id: '' })).toEqual([]) + expect(warn).not.toHaveBeenCalled() + writeDb(absent, 'session') + expect(listOpenCodeDbSessions(absent, { ms: 0, id: '' })).toHaveLength(1) + expect(warn).not.toHaveBeenCalled() + } finally { + warn.mockRestore() + } + }) + + it('still warns when the database exists but cannot be read', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + try { + writeFileSync(dbPath, 'this is not a sqlite database'.repeat(100)) + expect(listOpenCodeDbSessions(dbPath, { ms: 0, id: '' })).toEqual([]) + expect(warn).toHaveBeenCalledTimes(1) + } finally { + warn.mockRestore() + } + }) + + it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)( + 'warns when an existing database is behind an inaccessible directory', + () => { + writeDb(dbPath, 'session') + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + try { + chmodSync(dir, 0o000) + expect(listOpenCodeDbSessions(dbPath, { ms: 0, id: '' })).toEqual([]) + expect(warn).toHaveBeenCalledTimes(1) + expect(warn.mock.calls[0]?.[1]).toMatchObject({ code: 'EACCES' }) + } finally { + chmodSync(dir, 0o700) + warn.mockRestore() + } + } + ) }) diff --git a/src/main/opencode/opencode-session-binder.ts b/src/main/opencode/opencode-session-binder.ts index 0a693e6e928..8b62a05fa82 100644 --- a/src/main/opencode/opencode-session-binder.ts +++ b/src/main/opencode/opencode-session-binder.ts @@ -1,3 +1,4 @@ +import { statSync } from 'node:fs' import { resolveOpenCodeDataDirectory } from './opencode-data-directory' import { bindOpenCodeSession, @@ -225,6 +226,8 @@ export function listOpenCodeDbSessions( cursor: OpenCodeSessionCursor ): BinderSessionRow[] { try { + // Check absence without hiding permission failures as an unused store. + statSync(dbPath) return readOpenCodeDatabase({ dbPath, read: (db) => { @@ -269,6 +272,13 @@ export function listOpenCodeDbSessions( } }) } catch (err) { + if ( + err instanceof Error && + 'code' in err && + (err.code === 'ENOENT' || err.code === 'ENOTDIR') + ) { + return [] + } console.warn('[opencode-binder] session store read failed; skipping round', err) return [] } From a21d328cb9aabb51d7bd2eecd387c81053a818cd Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:56:59 -0700 Subject: [PATCH 12/25] fix(terminal): fill OpenCode DOM block glyphs in repainted rows (#24582) * fix(terminal): fill DOM block glyphs only in repainted rows Adapt the block-fill approach from PR #15955 and bound painting to xterm render ranges without observer or animation-frame rescans. Co-authored-by: mmarabel * fix(terminal): preserve block fills across DOM row replacements --------- Co-authored-by: mmarabel --- .../src/lib/pane-manager/pane-dom-creation.ts | 1 + .../lib/pane-manager/pane-lifecycle.test.ts | 2 + .../src/lib/pane-manager/pane-lifecycle.ts | 4 + .../lib/pane-manager/pane-manager-types.ts | 1 + .../pane-terminal-gpu-acceleration.ts | 5 + .../terminal-dom-block-fill.test.ts | 231 ++++++++++++++++++ .../pane-manager/terminal-dom-block-fill.ts | 172 +++++++++++++ 7 files changed, 416 insertions(+) create mode 100644 src/renderer/src/lib/pane-manager/terminal-dom-block-fill.test.ts create mode 100644 src/renderer/src/lib/pane-manager/terminal-dom-block-fill.ts diff --git a/src/renderer/src/lib/pane-manager/pane-dom-creation.ts b/src/renderer/src/lib/pane-manager/pane-dom-creation.ts index 6313a81cca7..cb7ef826b87 100644 --- a/src/renderer/src/lib/pane-manager/pane-dom-creation.ts +++ b/src/renderer/src/lib/pane-manager/pane-dom-creation.ts @@ -144,6 +144,7 @@ export function createPaneDOM( paneDragCleanup, compositionHandler: null, focusClassSyncCleanup: null, + domBlockFillCleanup: null, terminalScrollIntentDisposable: null, mouseEncodingTrackerDisposable, linkifierMouseLeaveResetDisposable: null, diff --git a/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts b/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts index c42a56d391f..52b44eade33 100644 --- a/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts +++ b/src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts @@ -44,10 +44,12 @@ function createPane(): ManagedPaneInternal { id: 1, leafId, stablePaneId: leafId, + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: stand-in for xterm's Terminal exposing only the members these lifecycle paths touch. terminal: { loadAddon: vi.fn(), attachCustomWheelEventHandler: vi.fn(), refresh: vi.fn(), + onRender: vi.fn(() => ({ dispose: vi.fn() })), cols: 80, rows: 24 } as never, diff --git a/src/renderer/src/lib/pane-manager/pane-lifecycle.ts b/src/renderer/src/lib/pane-manager/pane-lifecycle.ts index 4f9ac4a0be9..2e1122f8852 100644 --- a/src/renderer/src/lib/pane-manager/pane-lifecycle.ts +++ b/src/renderer/src/lib/pane-manager/pane-lifecycle.ts @@ -15,6 +15,7 @@ import { } from './terminal-linkifier-hover-reset-on-mouseleave' import { installTerminalLinkifierHoverResetOnWrite } from './terminal-linkifier-hover-reset-on-write' import { attachDomRendererFocusClassSync } from './pane-dom-focus-class-sync' +import { attachDomBlockFill } from './terminal-dom-block-fill' import { attachWebgl, cancelPendingWebglRefresh, disposeWebgl } from './pane-webgl-renderer' import { rebuildAttachedWebgl } from './pane-webgl-reattach' import { configureLazyArabicShapingJoiner } from './terminal-arabic-shaping-joiner' @@ -104,6 +105,7 @@ export function openTerminal( pane.compositionHandler = installTerminalImeCandidateAnchor(terminal) pane.focusClassSyncCleanup = attachDomRendererFocusClassSync(terminal.element) + pane.domBlockFillCleanup = attachDomBlockFill(terminal) // Configure the first atlas with ligatures instead of immediately rebuilding it. if (ligatures) { @@ -201,6 +203,8 @@ export function disposePane( pane.paneDragCleanup = null pane.focusClassSyncCleanup?.() pane.focusClassSyncCleanup = null + pane.domBlockFillCleanup?.() + pane.domBlockFillCleanup = null pane.terminalScrollIntentDisposable?.dispose() pane.terminalScrollIntentDisposable = null pane.mouseEncodingTrackerDisposable?.dispose() diff --git a/src/renderer/src/lib/pane-manager/pane-manager-types.ts b/src/renderer/src/lib/pane-manager/pane-manager-types.ts index fe4fcb5bc6f..3065a3c3704 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager-types.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager-types.ts @@ -200,6 +200,7 @@ export type ManagedPaneInternal = { compositionHandler: (() => void) | null // Stored so disposePane() can remove DOM-renderer focus synchronization. focusClassSyncCleanup?: (() => void) | null + domBlockFillCleanup?: (() => void) | null // Stored so disposePane() can remove user-scroll intent listeners. terminalScrollIntentDisposable?: IDisposable | null // Stored so disposePane() can drop the mouse-encoding parser handlers. diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.ts b/src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.ts index 099e3a40c82..d096352eb4e 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.ts @@ -6,6 +6,7 @@ import { shouldUseTerminalWebgl } from './pane-webgl-renderer' import { safeFit } from './pane-tree-ops' +import { applyDomBlockFills } from './terminal-dom-block-fill' import { resetPaneWebglContextLosses } from './pane-webgl-context-loss-policy' export function applyTerminalGpuAcceleration( @@ -32,6 +33,10 @@ export function applyTerminalGpuAcceleration( } if (!shouldUseTerminalWebgl(pane)) { disposeWebgl(pane, { refreshDimensions: true }) + const root = pane.terminal.element + if (root) { + applyDomBlockFills(root) + } continue } if ( diff --git a/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.test.ts b/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.test.ts new file mode 100644 index 00000000000..3a111750268 --- /dev/null +++ b/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.test.ts @@ -0,0 +1,231 @@ +// @vitest-environment happy-dom +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + applyDomBlockFills, + attachDomBlockFill, + backgroundImageForUniformBlockRun +} from './terminal-dom-block-fill' + +describe('backgroundImageForUniformBlockRun', () => { + it('fills the top half of the cell for a run of upper-half blocks', () => { + expect(backgroundImageForUniformBlockRun('▀▀▀')).toBe( + 'linear-gradient(to bottom, var(--orca-block-fg) 50%, transparent 50%)' + ) + }) + + it('fills the bottom half of the cell for lower-half blocks', () => { + expect(backgroundImageForUniformBlockRun('▄▄')).toBe( + 'linear-gradient(to bottom, transparent 50%, var(--orca-block-fg) 50%)' + ) + }) + + it('fills the whole cell for full blocks', () => { + expect(backgroundImageForUniformBlockRun('█')).toBe( + 'linear-gradient(to bottom, transparent 0%, var(--orca-block-fg) 0%)' + ) + }) + + it('fills the left half for left-half blocks', () => { + expect(backgroundImageForUniformBlockRun('▌')).toBe( + 'linear-gradient(to right, var(--orca-block-fg) 50%, transparent 50%)' + ) + }) + + it('fills the right half for right-half blocks', () => { + expect(backgroundImageForUniformBlockRun('▐')).toBe( + 'linear-gradient(to right, transparent 50%, var(--orca-block-fg) 50%)' + ) + }) + + it('fills the top eighth for upper one-eighth blocks', () => { + expect(backgroundImageForUniformBlockRun('▔')).toBe( + 'linear-gradient(to bottom, var(--orca-block-fg) 12.5%, transparent 12.5%)' + ) + }) + + it('fills the right eighth for right one-eighth blocks', () => { + expect(backgroundImageForUniformBlockRun('▕')).toBe( + 'linear-gradient(to right, transparent 87.5%, var(--orca-block-fg) 87.5%)' + ) + }) + + it('skips mixed runs, spaces, and ASCII', () => { + expect(backgroundImageForUniformBlockRun('▀█')).toBeNull() + expect(backgroundImageForUniformBlockRun('▀ ▀')).toBeNull() + expect(backgroundImageForUniformBlockRun('Ask')).toBeNull() + expect(backgroundImageForUniformBlockRun('')).toBeNull() + }) +}) + +describe('applyDomBlockFills', () => { + afterEach(() => { + document.body.replaceChildren() + }) + + function mountSpan(text: string, color = 'rgb(30, 30, 30)'): HTMLSpanElement { + const rows = document.createElement('div') + rows.className = 'xterm-rows' + const span = document.createElement('span') + span.textContent = text + span.style.color = color + span.style.backgroundColor = 'rgb(10, 10, 10)' + const row = document.createElement('div') + row.appendChild(span) + rows.appendChild(row) + document.body.appendChild(rows) + return span + } + + it('paints a uniform ▀ composer border with a cell-filling gradient', () => { + const span = mountSpan('▀'.repeat(12)) + + applyDomBlockFills(document) + + expect(span.style.color).toBe('transparent') + expect(span.style.backgroundImage).toContain('linear-gradient(to bottom') + expect(span.style.backgroundSize).toBe(`${100 / 12}% 100%`) + expect(span.style.backgroundRepeat).toBe('repeat-x') + expect(span.style.getPropertyValue('--orca-block-fg')).toBe('rgb(30, 30, 30)') + }) + + it('repeats a per-cell horizontal fill across a multi-cell left-half run', () => { + const span = mountSpan('▌▌') + + applyDomBlockFills(document) + + expect(span.style.backgroundImage).toContain('linear-gradient(to right') + expect(span.style.backgroundSize).toBe('50% 100%') + expect(span.style.backgroundRepeat).toBe('repeat-x') + }) + + it('repeats a per-cell horizontal fill across a multi-cell right-half run', () => { + const span = mountSpan('▐▐') + + applyDomBlockFills(document) + + expect(span.style.backgroundImage).toBe( + 'linear-gradient(to right, transparent 50%, var(--orca-block-fg) 50%)' + ) + expect(span.style.backgroundSize).toBe('50% 100%') + expect(span.style.backgroundRepeat).toBe('repeat-x') + }) + + it('does not restyle mixed block/letter spans', () => { + const span = mountSpan('▀▀█ Ask') + applyDomBlockFills(document) + expect(span.style.color).toBe('rgb(30, 30, 30)') + expect(span.style.backgroundImage).toBe('') + }) + + it('visits only the rows xterm repainted', () => { + const first = mountSpan('▀▀▀') + const rows = first.parentElement!.parentElement! + const secondRow = document.createElement('div') + const second = document.createElement('span') + second.textContent = '▄▄▄' + second.style.color = 'rgb(30, 30, 30)' + secondRow.appendChild(second) + rows.appendChild(secondRow) + const untouched = vi.spyOn(first, 'textContent', 'get') + + applyDomBlockFills(document, { start: 1, end: 1 }) + + expect(untouched).not.toHaveBeenCalled() + expect(first.style.backgroundImage).toBe('') + expect(second.style.backgroundImage).toContain('linear-gradient') + }) + + it('clears a previous fill when the span is reused for ordinary text', () => { + const span = mountSpan('▀▀▀') + applyDomBlockFills(document) + span.textContent = 'tab agents' + applyDomBlockFills(document) + expect(span.style.backgroundImage).toBe('') + expect(span.style.backgroundSize).toBe('') + expect(span.style.backgroundRepeat).toBe('') + expect(span.style.color).toBe('rgb(30, 30, 30)') + }) + + it('uses a color set on a reused filled span instead of the saved one', () => { + const span = mountSpan('▀▀▀') + applyDomBlockFills(document) + span.style.color = 'rgb(200, 0, 0)' + applyDomBlockFills(document) + expect(span.style.color).toBe('transparent') + expect(span.style.getPropertyValue('--orca-block-fg')).toBe('rgb(200, 0, 0)') + + span.textContent = 'tab agents' + applyDomBlockFills(document) + expect(span.style.color).toBe('rgb(200, 0, 0)') + }) + + it('keeps a color set after the fill when the span becomes ordinary text', () => { + const span = mountSpan('▀▀▀') + applyDomBlockFills(document) + span.style.color = 'rgb(0, 200, 0)' + span.textContent = 'tab agents' + applyDomBlockFills(document) + expect(span.style.color).toBe('rgb(0, 200, 0)') + expect(span.style.backgroundImage).toBe('') + }) + + it('does not pin an inherited color inline when clearing a fill', () => { + const span = mountSpan('▀▀▀', '') + span.parentElement!.style.color = 'rgb(5, 5, 5)' + applyDomBlockFills(document) + expect(span.style.getPropertyValue('--orca-block-fg')).toBe('rgb(5, 5, 5)') + + span.textContent = 'tab agents' + applyDomBlockFills(document) + expect(span.style.color).toBe('') + }) +}) + +describe('attachDomBlockFill', () => { + it('fills row replacements from focus/selection without rescanning untouched rows', async () => { + const element = document.createElement('div') + const rows = document.createElement('div') + rows.className = 'xterm-rows' + element.appendChild(rows) + const untouched = document.createElement('div') + const original = document.createElement('span') + original.textContent = 'ordinary text' + untouched.appendChild(original) + rows.appendChild(untouched) + const changed = document.createElement('div') + rows.appendChild(changed) + const detach = attachDomBlockFill({ element }) + const unread = vi.spyOn(original, 'textContent', 'get') + const span = document.createElement('span') + span.textContent = '▀▀' + span.style.color = 'rgb(1, 2, 3)' + changed.replaceChildren(span) + + await vi.waitFor(() => expect(span.style.color).toBe('transparent')) + expect(unread).not.toHaveBeenCalled() + detach() + const replacement = document.createElement('span') + replacement.textContent = '▄▄' + replacement.style.color = 'rgb(1, 2, 3)' + changed.replaceChildren(replacement) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(replacement.style.color).toBe('rgb(1, 2, 3)') + }) + + it('fills a new DOM renderer container after a GPU mode change', async () => { + const element = document.createElement('div') + const detach = attachDomBlockFill({ element }) + const rows = document.createElement('div') + rows.className = 'xterm-rows' + const row = document.createElement('div') + const span = document.createElement('span') + span.textContent = '▀▀' + span.style.color = 'rgb(1, 2, 3)' + row.appendChild(span) + rows.appendChild(row) + element.appendChild(rows) + + await vi.waitFor(() => expect(span.style.color).toBe('transparent')) + detach() + }) +}) diff --git a/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.ts b/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.ts new file mode 100644 index 00000000000..989d4f3c9d7 --- /dev/null +++ b/src/renderer/src/lib/pane-manager/terminal-dom-block-fill.ts @@ -0,0 +1,172 @@ +const BLOCK_FG_VAR = '--orca-block-fg' +const FILL_ATTR = 'data-orca-block-fill' +// The span's inline color before the fill ('' when it had none), restored on clear. +const INLINE_FG_ATTR = 'data-orca-block-inline-fg' + +function gradient(axis: 'bottom' | 'right', solidPercent: number, fromStart: boolean): string { + const dir = axis === 'bottom' ? 'to bottom' : 'to right' + const fg = `var(${BLOCK_FG_VAR})` + if (fromStart) { + return `linear-gradient(${dir}, ${fg} ${solidPercent}%, transparent ${solidPercent}%)` + } + const gap = 100 - solidPercent + return `linear-gradient(${dir}, transparent ${gap}%, ${fg} ${gap}%)` +} + +/** CSS background-image that fills the cell the way WebGL custom glyphs do. */ +export function backgroundImageForUniformBlockRun(text: string): string | null { + if (text.length === 0) { + return null + } + const ch = text[0]! + for (let i = 1; i < text.length; i++) { + if (text[i] !== ch) { + return null + } + } + const code = ch.codePointAt(0) ?? 0 + if (code === 0x2580) { + return gradient('bottom', 50, true) + } + if (code >= 0x2581 && code <= 0x2588) { + const eighths = code - 0x2580 + return gradient('bottom', eighths * 12.5, false) + } + if (code >= 0x2589 && code <= 0x258f) { + const eighths = 8 - (code - 0x2588) + return gradient('right', eighths * 12.5, true) + } + if (code === 0x2590) { + return gradient('right', 50, false) + } + if (code === 0x2594) { + return gradient('bottom', 12.5, true) + } + if (code === 0x2595) { + return gradient('right', 12.5, false) + } + return null +} + +function clearBlockFill(span: HTMLElement): void { + if (!span.hasAttribute(FILL_ATTR)) { + return + } + const originalColor = span.getAttribute(INLINE_FG_ATTR) + // Any other inline color was set after the fill and is the span's current foreground. + const ownsColor = span.style.color === 'transparent' + span.removeAttribute(FILL_ATTR) + span.removeAttribute(INLINE_FG_ATTR) + span.style.removeProperty('background-image') + span.style.removeProperty('background-size') + span.style.removeProperty('background-repeat') + span.style.removeProperty(BLOCK_FG_VAR) + if (!ownsColor) { + return + } + if (originalColor) { + span.style.color = originalColor + } else { + span.style.removeProperty('color') + } +} + +type RenderedRows = { start: number; end: number } + +export function applyDomBlockFills(root: ParentNode, range?: RenderedRows): void { + if (typeof root.querySelectorAll !== 'function') { + return + } + const rows = root.querySelector('.xterm-rows') + if (!rows) { + return + } + const start = range?.start ?? 0 + const end = Math.min(range?.end ?? rows.children.length - 1, rows.children.length - 1) + for (let row = start; row <= end; row++) { + fillBlockRow(rows.children[row]!) + } +} + +function fillBlockRow(row: Element): void { + for (const span of row.querySelectorAll('span')) { + fillBlockSpan(span) + } +} + +function fillBlockSpan(span: HTMLElement): void { + const text = span.textContent ?? '' + const fill = backgroundImageForUniformBlockRun(text) + if (!fill) { + clearBlockFill(span) + return + } + const inlineFg = span.style.color + const ownsColor = span.hasAttribute(FILL_ATTR) && inlineFg === 'transparent' + if (ownsColor && span.getAttribute(FILL_ATTR) === text) { + return + } + const fg = ownsColor + ? span.style.getPropertyValue(BLOCK_FG_VAR) + : inlineFg || (typeof getComputedStyle === 'function' ? getComputedStyle(span).color : '') + if (!fg) { + return + } + if (!ownsColor) { + span.setAttribute(INLINE_FG_ATTR, inlineFg) + } + span.setAttribute(FILL_ATTR, text) + span.style.setProperty(BLOCK_FG_VAR, fg) + span.style.color = 'transparent' + span.style.backgroundImage = fill + span.style.backgroundSize = `${100 / text.length}% 100%` + span.style.backgroundRepeat = 'repeat-x' +} + +function collectAddedBlockRows(node: Node, rows: Set): void { + if (!(node instanceof HTMLElement)) { + return + } + if (node.parentElement?.classList.contains('xterm-rows')) { + rows.add(node) + return + } + const containers = node.matches('.xterm-rows') ? [node] : node.querySelectorAll('.xterm-rows') + for (const container of containers) { + for (const row of container.children) { + if (row instanceof HTMLElement) { + rows.add(row) + } + } + } +} + +export function attachDomBlockFill(terminal: { element?: HTMLElement | undefined }): () => void { + const root = terminal.element + if (!root || typeof MutationObserver === 'undefined') { + return () => undefined + } + applyDomBlockFills(root) + // DOM focus, selection and link paints replace rows without xterm's public onRender event. + const observer = new MutationObserver((records) => { + const rows = new Set() + for (const record of records) { + const row = + record.target instanceof HTMLElement ? record.target.closest('.xterm-rows > div') : null + if (row instanceof HTMLElement) { + rows.add(row) + continue + } + for (const node of record.addedNodes) { + collectAddedBlockRows(node, rows) + } + } + for (const row of rows) { + if (root.contains(row)) { + fillBlockRow(row) + } + } + }) + observer.observe(root, { childList: true, subtree: true }) + return () => observer.disconnect() +} From d140b72c29903bdbd2a73e05019ff832ec6ecd39 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:57:03 -0700 Subject: [PATCH 13/25] fix(terminal): pass single-pane TUI tab clicks through drag strip (#24591) --- src/renderer/src/assets/terminal.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/assets/terminal.css b/src/renderer/src/assets/terminal.css index 7b470a252ed..5d6335ebb8d 100644 --- a/src/renderer/src/assets/terminal.css +++ b/src/renderer/src/assets/terminal.css @@ -118,6 +118,7 @@ /* ── Pane drag handle ─────────────────────────────────── */ .pane-drag-handle { + pointer-events: none; position: absolute; top: 0; left: 0; From 8186ded0bd5549c7bcca6f69d83e18565e1c8648 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 04:57:06 -0700 Subject: [PATCH 14/25] fix(mobile): preserve terminal mode query variables in WebView builds (#24626) --- .../scripts/build-terminal-webview-engine.mjs | 5 +++- .../terminal/terminal-webview-engine.test.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mobile/scripts/build-terminal-webview-engine.mjs b/mobile/scripts/build-terminal-webview-engine.mjs index da81686671f..c6188c14b5a 100644 --- a/mobile/scripts/build-terminal-webview-engine.mjs +++ b/mobile/scripts/build-terminal-webview-engine.mjs @@ -81,7 +81,10 @@ async function buildEngineJs() { }, bundle: true, format: 'iife', - minify: true, + // esbuild 0.25 syntax folding drops xterm's local DECRQM enum declaration. + minifyWhitespace: true, + minifyIdentifiers: true, + minifySyntax: false, platform: 'browser', target, legalComments: 'none', diff --git a/mobile/src/terminal/terminal-webview-engine.test.ts b/mobile/src/terminal/terminal-webview-engine.test.ts index 96a4327f090..bea9e2e1ca0 100644 --- a/mobile/src/terminal/terminal-webview-engine.test.ts +++ b/mobile/src/terminal/terminal-webview-engine.test.ts @@ -91,6 +91,35 @@ describe('terminal WebView bundled engine', () => { expect(() => parse(XTERM_ENGINE_JS, { ecmaVersion: 2019 })).not.toThrow() }) + it('answers the mode query OpenCode sends during startup', async () => { + vi.useFakeTimers() + const reply = vi.fn() + const window = {} + try { + new Script( + `${XTERM_ENGINE_JS}; const terminal = new window.Terminal(); + terminal.onData(reply); terminal.write('\\x1b[?1004$p');` + ).runInNewContext({ + window, + self: window, + document: {}, + navigator: { platform: 'Linux', userAgent: 'Chrome/74' }, + structuredClone, + performance, + setTimeout, + clearTimeout, + queueMicrotask, + console, + URL, + reply + }) + await vi.runAllTimersAsync() + expect(reply).toHaveBeenCalledWith('\x1b[?1004;2$y') + } finally { + vi.useRealTimers() + } + }) + // Why: the context deliberately omits WeakRef (Chrome 84+) / structuredClone // (Chrome 98+) and supplies an Element without replaceChildren (Chrome 86+) — // the engine must evaluate on older WebViews via its own guarded runtime shims, From ff452661e77fdf0494df76efb1ce70be1d363568 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 05:01:18 -0700 Subject: [PATCH 15/25] Stop stalled unit jobs after an hour (#24583) * ci: bound unit jobs to one hour of execution * docs: keep CI budget notes clear of the headless follow-up * docs: keep CI deadline evidence in the pull request --- .github/workflows/unit-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index abe9cf28a41..2e6f173acc4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -26,6 +26,7 @@ jobs: test: name: tests node ${{ matrix.node }} ${{ matrix.shard.index }}/${{ matrix.shard.count }} runs-on: ${{ inputs.runner }} + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -82,6 +83,7 @@ jobs: matrix: node: ${{ fromJSON(inputs.node_versions) }} runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Checkout From 8765f8c9b1f4f83b7b6d058d03e089627fb93d5f Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 05:02:03 -0700 Subject: [PATCH 16/25] fix(opencode): preserve turn outcomes and avoid auto permission attention (#24612) * fix(opencode): retain failed and stopped TUI turn outcomes Adapt the root verdict proposal from brennanb2025 in PR #23105 to the current TUI-owned lifecycle, keeping hook-store authority and existing mainAgent semantics. * fix(opencode): let auto-approved permissions settle before attention * fix(opencode): reconcile cached outcomes with completed session turns * fix(opencode): bind terminal verdicts to ending event timestamps * fix(opencode): publish approval cards for permission requests --- .../server-opencode-normalization.test.ts | 40 +++ ...ook-plugin-opencode2-tui-ownership.test.ts | 254 +++++++++--------- .../opencode/opencode-tui-session-fixture.ts | 156 +++++++++++ .../opencode2/status-plugin-tui-source.ts | 71 +++-- .../providers/opencode-family-events.ts | 27 +- .../providers/opencode-family-tool-fields.ts | 7 +- src/shared/opencode-permission-status.test.ts | 19 +- 7 files changed, 428 insertions(+), 146 deletions(-) create mode 100644 src/main/opencode/opencode-tui-session-fixture.ts diff --git a/src/main/agent-hooks/server-opencode-normalization.test.ts b/src/main/agent-hooks/server-opencode-normalization.test.ts index 56bb0b063b8..e1ff6dfbcc6 100644 --- a/src/main/agent-hooks/server-opencode-normalization.test.ts +++ b/src/main/agent-hooks/server-opencode-normalization.test.ts @@ -63,6 +63,46 @@ describe.each(['opencode', 'opencode2'] as const)('%s hook normalization', (sour expect(result?.payload.agentType).toBe(source) }) + it.each([ + ['UnknownError', 'failure', undefined], + ['MessageAbortedError', 'cancellation', true] + ])('retains the reported root outcome for %s', (errorName, outcome, interrupted) => { + const result = _internals.normalizeHookPayload( + source, + buildBody({ + hook_event_name: 'SessionIdle', + root_state: 'done', + root_turn_error_name: errorName + }), + 'production' + ) + expect(result?.payload.mainAgent).toMatchObject({ state: 'done', outcome }) + expect(result?.payload.interrupted).toBe(interrupted) + }) + + it('does not infer an outcome for an older plugin', () => { + const result = _internals.normalizeHookPayload( + source, + buildBody({ hook_event_name: 'SessionIdle' }), + 'production' + ) + expect(result?.payload.mainAgent).toBeUndefined() + }) + + it('keeps a root failure beside still-working child work', () => { + const result = _internals.normalizeHookPayload( + source, + buildBody({ + hook_event_name: 'SessionBusy', + root_state: 'done', + root_turn_error_name: 'api' + }), + 'production' + ) + expect(result?.payload.state).toBe('working') + expect(result?.payload.mainAgent).toMatchObject({ state: 'done', outcome: 'failure' }) + }) + it('PermissionRequest maps to waiting', () => { const result = _internals.normalizeHookPayload( source, diff --git a/src/main/opencode/hook-plugin-opencode2-tui-ownership.test.ts b/src/main/opencode/hook-plugin-opencode2-tui-ownership.test.ts index 29eefd77482..cca4712e971 100644 --- a/src/main/opencode/hook-plugin-opencode2-tui-ownership.test.ts +++ b/src/main/opencode/hook-plugin-opencode2-tui-ownership.test.ts @@ -15,13 +15,18 @@ vi.mock('electron', () => ({ })) import { _internals } from './hook-service' +import { fakeTui, type BusEvent } from './opencode-tui-session-fixture' type Post = { paneKey?: string opencodeMajor?: number - payload?: { hook_event_name?: string; sessionID?: string } + payload?: { + hook_event_name?: string + sessionID?: string + root_state?: string + root_turn_error_name?: string + } } -type BusEvent = { type: string; data: Record } type PluginModule = { default?: { setup?: (ctx: unknown) => Promise<(() => Promise) | undefined> } } @@ -70,130 +75,6 @@ function turn(sessionID: string, text: string): { start: BusEvent[]; finish: Bus } } -type Blocker = { id: string; sessionID: string; [key: string]: unknown } - -function toBlocker(value: unknown): Blocker { - const record = typeof value === 'object' && value !== null ? { ...value } : {} - const id = 'id' in record ? String(record.id) : '' - const sessionID = 'sessionID' in record ? String(record.sessionID) : '' - return { ...record, id, sessionID } -} - -/** One pane's TUI: its route, its view of the shared session store, and the shared event bus. */ -function fakeTui(version = '2.0.14') { - const listeners = new Set<(event: { details: BusEvent }) => void>() - const sessions = new Map() - const running = new Set() - const permissions = new Map() - const forms = new Map() - // What the server answers when the TUI re-fetches a permission list (reconnect). - const serverPermissions = new Map() - let permissionFetch: Promise = Promise.resolve() - // Why: OpenCode keeps storage.memory across plugin hot reloads within one TUI process. - const memories = new Map() - let route: { type: string; sessionID?: string } = { type: 'home' } - const rootOf = (id: string): string => { - let current = sessions.get(id) - while (current?.parentID && sessions.has(current.parentID)) { - current = sessions.get(current.parentID) - } - return current?.id ?? id - } - const without = (map: Map, sessionID: string, id: unknown): void => { - map.set( - sessionID, - (map.get(sessionID) ?? []).filter((item) => item.id !== id) - ) - } - const listen = vi.fn((handler: (event: { details: BusEvent }) => void) => { - listeners.add(handler) - return () => listeners.delete(handler) - }) - const ctx = { - app: { version, channel: 'latest' }, - ui: { router: { current: () => route } }, - storage: { - memory: (key: string, options: { initial: Record }) => { - if (!memories.has(key)) { - const value = structuredClone(options.initial) - memories.set(key, [value, (mutate: (draft: typeof value) => void) => mutate(value)]) - } - return memories.get(key) - } - }, - client: { - session: { get: async ({ sessionID }: { sessionID: string }) => sessions.get(sessionID) } - }, - data: { - listen, - session: { - get: (id: string) => sessions.get(id), - root: rootOf, - family: (id: string) => - [...sessions.keys()].filter((member) => rootOf(member) === rootOf(id)), - status: (id: string) => (running.has(id) ? 'running' : 'idle'), - permission: { - list: (id: string) => permissions.get(id), - sync: async (id: string) => { - await permissionFetch - permissions.set(id, [...(serverPermissions.get(id) ?? [])]) - } - }, - form: { list: (id: string) => forms.get(id), sync: async () => {} } - } - } - } - return { - ctx, - listen, - serverPermissions, - permissions, - navigate(sessionID: string) { - route = { type: 'session', sessionID } - }, - // The session data stops reporting a run without this TUI seeing its end event. - loseEnd(sessionID: string) { - running.delete(sessionID) - }, - // The session data reports a run whose start this TUI never saw. - loseStart(sessionID: string) { - running.add(sessionID) - }, - // Holds the next permission list fetch until the returned release is called. - holdPermissionFetch(): () => void { - let release = (): void => {} - permissionFetch = new Promise((resolve) => { - release = resolve - }) - return release - }, - // Applies an event to the session data first, then to plugin listeners, as OpenCode does. - emit(event: BusEvent) { - const sessionID = String(event.data.sessionID) - if (event.type === 'session.created') { - const parentID = typeof event.data.parentID === 'string' ? event.data.parentID : undefined - sessions.set(sessionID, { id: sessionID, parentID }) - } else if (event.type === 'session.execution.started') { - running.add(sessionID) - } else if (event.type === 'session.execution.succeeded') { - running.delete(sessionID) - } else if (event.type === 'permission.asked') { - permissions.set(sessionID, [...(permissions.get(sessionID) ?? []), toBlocker(event.data)]) - } else if (event.type === 'permission.replied') { - without(permissions, sessionID, event.data.requestID) - } else if (event.type === 'form.created') { - const form = toBlocker(event.data.form) - forms.set(form.sessionID, [...(forms.get(form.sessionID) ?? []), form]) - } else if (event.type === 'form.replied' || event.type === 'form.cancelled') { - without(forms, sessionID, event.data.id) - } - for (const handler of listeners) { - handler({ details: event }) - } - } - } -} - describe('OpenCode 2 TUI reporter: each pane reports its own sessions', () => { let tempDir: string let savedFetch: typeof globalThis.fetch @@ -290,6 +171,33 @@ describe('OpenCode 2 TUI reporter: each pane reports its own sessions', () => { } } + it.each([ + ['session.execution.failed', { error: { type: 'api' } }, 'api'], + ['session.execution.failed', {}, 'UnknownError'], + ['session.execution.interrupted', { reason: 'user' }, 'MessageAbortedError'], + ['session.execution.interrupted', { reason: 'pause' }, undefined], + ['session.execution.succeeded', {}, undefined] + ])('carries the root verdict for %s', async (type, fields, errorName) => { + const reported = await runPane(PANE_A, SES_A, async (tui) => { + tui.navigate(SES_A) + await pump(tui, turn(SES_A, 'root').start) + await pump(tui, [{ type, data: { sessionID: SES_A, ...fields } }]) + }) + expect(reported.at(-1)?.payload).toMatchObject({ root_state: 'done' }) + expect(reported.at(-1)?.payload?.root_turn_error_name).toBe(errorName) + }) + + it('clears a failed root verdict on its next turn', async () => { + const reported = await runPane(PANE_A, SES_A, async (tui) => { + tui.navigate(SES_A) + await pump(tui, turn(SES_A, 'first').start) + await pump(tui, [{ type: 'session.execution.failed', data: { sessionID: SES_A } }]) + await pump(tui, [{ type: 'session.execution.started', data: { sessionID: SES_A } }]) + await pump(tui, turn(SES_A, 'next').finish) + }) + expect(reported.at(-1)?.payload?.root_turn_error_name).toBeUndefined() + }) + // Captured s2 shape: pane A's long turn overlapped by pane B's short one on one server. it('gives each overlapping pane only its own session and its own Idle', async () => { const a = turn(SES_A, 'A long SLEEP=12') @@ -446,6 +354,75 @@ describe('OpenCode 2 TUI reporter: each pane reports its own sessions', () => { data: { id: 'per_1', sessionID, action: 'bash', resources: ['rm -rf build'] } }) + it('publishes a root failure while its child keeps the aggregate Working', async () => { + const tui = fakeTui() + const cleanup = await start(tui) + tui.navigate(SES_A) + await pump(tui, [...turn(SES_A, 'root').start, ...childStart(SES_A)]) + await pump(tui, [{ type: 'session.execution.failed', data: { sessionID: SES_A } }]) + await vi.waitFor(() => { + expect(posts.at(-1)?.payload).toMatchObject({ + hook_event_name: 'SessionBusy', + root_state: 'done', + root_turn_error_name: 'UnknownError' + }) + }) + await pump(tui, [{ type: 'session.execution.succeeded', data: { sessionID: SES_CHILD } }]) + expect(posts.at(-1)?.payload?.root_turn_error_name).toBe('UnknownError') + await cleanup?.() + }) + + it('does not turn a child failure into a root failure', async () => { + const reported = await runPane(PANE_A, SES_A, async (tui) => { + tui.navigate(SES_A) + await pump(tui, [...turn(SES_A, 'root').start, ...childStart(SES_A)]) + await pump(tui, [{ type: 'session.execution.failed', data: { sessionID: SES_CHILD } }]) + await pump(tui, turn(SES_A, 'root').finish) + }) + expect(reported.at(-1)?.payload?.root_turn_error_name).toBeUndefined() + }) + + it('keeps a failed terminal verdict through plugin hot reload', async () => { + const tui = fakeTui() + const first = await start(tui) + tui.navigate(SES_A) + await pump(tui, turn(SES_A, 'root').start) + await pump(tui, [{ type: 'session.execution.failed', data: { sessionID: SES_A } }]) + await vi.waitFor(() => + expect(posts.at(-1)?.payload?.root_turn_error_name).toBe('UnknownError') + ) + await first?.() + const before = posts.length + const second = await start(tui) + await tick(150) + expect(posts).toHaveLength(before) + expect(posts.at(-1)?.payload?.root_turn_error_name).toBe('UnknownError') + await second?.() + }) + + it.each(['session.execution.succeeded', 'session.execution.interrupted'])( + 'repairs a failed verdict when %s follows a missed turn start during unload', + async (finishType) => { + const tui = fakeTui() + const first = await start(tui) + tui.navigate(SES_A) + await pump(tui, turn(SES_A, 'first').start) + await pump(tui, [{ type: 'session.execution.failed', data: { sessionID: SES_A } }]) + await first?.() + await pump(tui, [ + { type: 'session.execution.started', data: { sessionID: SES_A } }, + { type: finishType, data: { sessionID: SES_A, reason: 'user' } } + ]) + const second = await start(tui) + await vi.waitFor(() => expect(posts.at(-1)?.payload?.root_turn_error_name).toBeUndefined()) + expect(posts.at(-1)?.payload).toMatchObject({ + hook_event_name: 'SessionIdle', + root_state: 'done' + }) + await second?.() + } + ) + // Why: #23700 left this for TUI panes; a reload that misses the end must still reach Done. it('shows Done for a turn that ended while the plugin was reloading', async () => { const tui = fakeTui() @@ -591,13 +568,36 @@ describe('OpenCode 2 TUI reporter: each pane reports its own sessions', () => { const a = turn(SES_A, 'A spawns a background task') await pump(tui, [...a.start, ...childStart(SES_A), ...a.finish]) await tick(250) - expect(statuses(posts)).toEqual([`SessionBusy:${SES_A}`]) + expect(statuses(posts)).toEqual([`SessionBusy:${SES_A}`, `SessionBusy:${SES_A}`]) + expect(posts.at(-1)?.payload).toMatchObject({ root_state: 'done' }) tui.loseEnd(SES_CHILD) await vi.waitFor(() => expect(summary(posts).at(-1)).toBe(`SessionIdle:${SES_A}`)) await cleanup?.() expect(posts.every((post) => post.payload?.sessionID === SES_A)).toBe(true) }) + it('keeps automatically answered permissions Working without an attention post', async () => { + const tui = fakeTui() + const cleanup = await start(tui) + tui.navigate(SES_A) + await pump(tui, turn(SES_A, 'auto').start) + for (let index = 0; index < 3; index += 1) { + tui.emit({ + type: 'permission.asked', + data: { ...permission(SES_A).data, id: `auto_${index}` } + }) + await tick(20) + tui.emit({ + type: 'permission.replied', + data: { sessionID: SES_A, requestID: `auto_${index}` } + }) + } + await tick(650) + expect(statuses(posts)).toEqual([`SessionBusy:${SES_A}`]) + await pump(tui, turn(SES_A, 'auto').finish) + await cleanup?.() + }) + it("shows a child's permission as Needs input on the root, then Working after the reply", async () => { const tui = fakeTui() const cleanup = await start(tui) diff --git a/src/main/opencode/opencode-tui-session-fixture.ts b/src/main/opencode/opencode-tui-session-fixture.ts new file mode 100644 index 00000000000..2b87635a523 --- /dev/null +++ b/src/main/opencode/opencode-tui-session-fixture.ts @@ -0,0 +1,156 @@ +import { vi } from 'vitest' + +export type BusEvent = { type: string; data: Record; created?: number } + +type Blocker = { id: string; sessionID: string; [key: string]: unknown } + +function toBlocker(value: unknown): Blocker { + const record = typeof value === 'object' && value !== null ? { ...value } : {} + const id = 'id' in record ? String(record.id) : '' + const sessionID = 'sessionID' in record ? String(record.sessionID) : '' + return { ...record, id, sessionID } +} + +/** One pane's TUI: its route, its view of the shared session store, and the shared event bus. */ +export function fakeTui(version = '2.0.14') { + const listeners = new Set<(event: { details: BusEvent }) => void>() + const sessions = new Map< + string, + { id: string; parentID?: string; outcome?: string; time?: { idle: number } } + >() + let idleClock = 1 + const running = new Set() + const permissions = new Map() + const forms = new Map() + // What the server answers when the TUI re-fetches a permission list (reconnect). + const serverPermissions = new Map() + let permissionFetch: Promise = Promise.resolve() + // Why: OpenCode keeps storage.memory across plugin hot reloads within one TUI process. + const memories = new Map() + let route: { type: string; sessionID?: string } = { type: 'home' } + const rootOf = (id: string): string => { + let current = sessions.get(id) + while (current?.parentID && sessions.has(current.parentID)) { + current = sessions.get(current.parentID) + } + return current?.id ?? id + } + const without = (map: Map, sessionID: string, id: unknown): void => { + map.set( + sessionID, + (map.get(sessionID) ?? []).filter((item) => item.id !== id) + ) + } + const listen = vi.fn((handler: (event: { details: BusEvent }) => void) => { + listeners.add(handler) + return () => listeners.delete(handler) + }) + const ctx = { + app: { version, channel: 'latest' }, + ui: { router: { current: () => route } }, + storage: { + memory: (key: string, options: { initial: Record }) => { + if (!memories.has(key)) { + const value = structuredClone(options.initial) + memories.set(key, [value, (mutate: (draft: typeof value) => void) => mutate(value)]) + } + return memories.get(key) + } + }, + client: { + session: { get: async ({ sessionID }: { sessionID: string }) => sessions.get(sessionID) } + }, + data: { + listen, + session: { + get: (id: string) => sessions.get(id), + root: rootOf, + family: (id: string) => + [...sessions.keys()].filter((member) => rootOf(member) === rootOf(id)), + status: (id: string) => (running.has(id) ? 'running' : 'idle'), + permission: { + list: (id: string) => permissions.get(id), + sync: async (id: string) => { + await permissionFetch + permissions.set(id, [...(serverPermissions.get(id) ?? [])]) + } + }, + form: { list: (id: string) => forms.get(id), sync: async () => {} } + } + } + } + return { + ctx, + listen, + serverPermissions, + permissions, + navigate(sessionID: string) { + route = { type: 'session', sessionID } + }, + // The session data stops reporting a run without this TUI seeing its end event. + loseEnd(sessionID: string) { + running.delete(sessionID) + }, + // The session data reports a run whose start this TUI never saw. + loseStart(sessionID: string) { + running.add(sessionID) + const session = sessions.get(sessionID) + if (session) { + session.outcome = undefined + } + }, + // Holds the next permission list fetch until the returned release is called. + holdPermissionFetch(): () => void { + let release = (): void => {} + permissionFetch = new Promise((resolve) => { + release = resolve + }) + return release + }, + // Ending events reach listeners before the session snapshot catches up. + emit(event: BusEvent) { + const sessionID = String(event.data.sessionID) + let notified = false + if (event.type === 'session.created') { + const parentID = typeof event.data.parentID === 'string' ? event.data.parentID : undefined + sessions.set(sessionID, { id: sessionID, parentID }) + } else if (event.type === 'session.execution.started') { + running.add(sessionID) + const session = sessions.get(sessionID) + if (session) { + session.outcome = undefined + } + } else if ( + event.type === 'session.execution.succeeded' || + event.type === 'session.execution.failed' || + event.type === 'session.execution.interrupted' + ) { + event = { ...event, created: ++idleClock } + for (const handler of listeners) { + handler({ details: event }) + } + notified = true + running.delete(sessionID) + const session = sessions.get(sessionID) + if (session) { + session.outcome = event.type.slice('session.execution.'.length) + session.time = { idle: idleClock } + } + } else if (event.type === 'permission.asked') { + permissions.set(sessionID, [...(permissions.get(sessionID) ?? []), toBlocker(event.data)]) + } else if (event.type === 'permission.replied') { + without(permissions, sessionID, event.data.requestID) + } else if (event.type === 'form.created') { + const form = toBlocker(event.data.form) + forms.set(form.sessionID, [...(forms.get(form.sessionID) ?? []), form]) + } else if (event.type === 'form.replied' || event.type === 'form.cancelled') { + without(forms, sessionID, event.data.id) + } + if (!notified) { + for (const handler of listeners) { + handler({ details: event }) + } + } + } + } +} diff --git a/src/main/opencode2/status-plugin-tui-source.ts b/src/main/opencode2/status-plugin-tui-source.ts index aff056d45de..f3d7db13a1e 100644 --- a/src/main/opencode2/status-plugin-tui-source.ts +++ b/src/main/opencode2/status-plugin-tui-source.ts @@ -8,6 +8,7 @@ export function getOpenCode2TuiSource(): string[] { return String.raw` const TUI_TICK_MS = 100; +const TUI_PERMISSION_SETTLE_MS = 500; const TUI_EARLY_ROOTS_MAX = 32; const TUI_RESOLVED_REQUESTS_MAX = 256; const TUI_ENDPOINT_CHECK_TICKS = 50; @@ -22,10 +23,9 @@ function boundedSet(set, value, max) { if (set.size > max) set.delete(set.values().next().value); } -// Why storage.memory: OpenCode keeps it across plugin hot reloads and drops it when the TUI -// exits, so a reload mid-turn keeps this pane's sessions and what it last reported. +// Memory survives hot reloads and ends with the TUI. function paneStatusMemory(ctx) { - const initial = { owned: [], last: "idle:", lastRoot: "", started: false }; + const initial = { owned: [], last: "idle:", lastRoot: "", started: false, endings: [] }; if (typeof ctx.storage?.memory === "function") return ctx.storage.memory("pane-status", { initial }); // Why started: without memory a reload looks like a TUI start, and must not reset the pane. const local = { ...initial, started: true }; @@ -50,10 +50,10 @@ async function setupOpenCode2Tui(ctx) { let disposed = false; let lastLevel = null; let ticks = 0; - // Root sessions this TUI saw start before it owned them: their SessionStart and prompt. const early = new Map(); // Why: a permission/form list fetched on reconnect can land after the reply event and restore it. const resolved = new Set(); + const permissionSeenAt = new Map(); const rootOf = (sessionID) => data.root(sessionID) || sessionID; const family = (root) => new Set([root, ...(typeof data.family === "function" ? data.family(root) || [] : [])]); @@ -62,18 +62,41 @@ async function setupOpenCode2Tui(ctx) { const route = ctx.ui.router.current(); return route?.type === "session" && typeof route.sessionID === "string" ? rootOf(route.sessionID) : undefined; }; - // First open permission, else first form, across the root's family. - const blocker = (root) => { + const blocker = (root, seenPermissions) => { let form; for (const member of family(root)) { - const permission = (data.permission?.list?.(member) || []).find((request) => !resolved.has(request.id)); + const permission = (data.permission?.list?.(member) || []).find((request) => { + if (resolved.has(request.id)) return false; + seenPermissions.add(request.id); + if (!permissionSeenAt.has(request.id)) permissionSeenAt.set(request.id, Date.now()); + // Auto replies arrive after the request; only an unanswered request needs attention. + return Date.now() - permissionSeenAt.get(request.id) >= TUI_PERMISSION_SETTLE_MS; + }); if (permission) return { request: permission, isPermission: true }; form ??= (data.form?.list?.(member) || []).find((request) => !resolved.has(request.id)); } return form ? { request: form, isPermission: false } : null; }; const levelKey = (level) => - level.kind === "waiting" ? "waiting:" + level.blocker.request.id + ":" + level.root : level.kind + ":" + level.root; + (level.kind === "waiting" ? "waiting:" + level.blocker.request.id + ":" + level.root : level.kind + ":" + level.root) + + (level.rootFields.root_state ? ":" + JSON.stringify(level.rootFields) : ""); + + function rootFields(level) { + if (!level.root) return {}; + const rootRunning = data.status(level.root) === "running"; + const rootState = rootRunning + ? level.kind === "waiting" && level.blocker.request.sessionID === level.root ? "waiting" : "working" + : "done"; + const session = data.get?.(level.root); + const ending = (memory.endings || []).find(([id]) => id === level.root); + const idleAt = session?.time?.idle; + const sameTurn = Number.isFinite(idleAt) && ending?.[2] === idleAt; + // Current session data repairs a whole turn missed while the plugin was unloaded. + const errorName = !rootRunning && (session?.outcome === "failed" + ? (sameTurn && ending[1]) || "UnknownError" + : session?.outcome === "interrupted" && sameTurn ? ending[1] : ""); + return { root_state: rootState, ...(errorName ? { root_turn_error_name: errorName } : {}) }; + } function own(root, owned) { const seen = early.get(root); @@ -94,11 +117,17 @@ async function setupOpenCode2Tui(ctx) { owned = owned.filter((root) => root === route || running(root)); if (owned.join("\n") !== memory.owned.join("\n")) setMemory((draft) => { draft.owned = owned; }); const active = owned.filter(running); + const seenPermissions = new Set(); + let waiting; for (const root of active) { // Why only while running: a blocker the session data kept after its turn ended is stale. - const found = blocker(root); - if (found) return { kind: "waiting", root, blocker: found }; + const found = blocker(root, seenPermissions); + if (found && !waiting) waiting = { kind: "waiting", root, blocker: found }; } + for (const id of permissionSeenAt.keys()) { + if (!seenPermissions.has(id)) permissionSeenAt.delete(id); + } + if (waiting) return waiting; const busy = active.at(-1); return busy ? { kind: "busy", root: busy } : { kind: "idle", root: memory.lastRoot }; } @@ -108,6 +137,7 @@ async function setupOpenCode2Tui(ctx) { let level; try { level = derive(); + level.rootFields = rootFields(level); } catch { // Why: a data read that throws must not kill the listener or the tick. return; @@ -119,14 +149,13 @@ async function setupOpenCode2Tui(ctx) { if (level.kind !== "idle") draft.lastRoot = level.root; }); lastLevel = level; - // Why the lifecycle queue: posts keep the order the levels were derived in. void enqueueLifecycle(() => deliver(level, true)); } async function deliver(level, changed) { // Retires assistant text queued under the previous level (see flushPendingAssistantPart). if (changed) stateArrivalRevision += 1; - const properties = level.root ? { sessionID: level.root } : {}; + const properties = level.root ? { sessionID: level.root, ...level.rootFields } : {}; if (level.kind === "waiting") { const { request, isPermission } = level.blocker; const translated = isPermission @@ -134,12 +163,12 @@ async function setupOpenCode2Tui(ctx) { : translateOpenCode2Event("form.created", { form: request }); if (!translated) return; const hookEventName = isPermission ? "PermissionRequest" : "AskUserQuestion"; - await setAttention(hookEventName, { ...translated.properties, ...properties }, factoryID, request.sessionID); + await flushPendingAssistantPart(true); + await setDeliveryTarget("waiting", levelKey(level), hookEventName, { ...translated.properties, ...properties }, factoryID); return; } - // Why flush first: the done-state preview must show the completed reply. if (level.kind === "idle") await flushPendingAssistantPart(true); - await setStatus(level.kind, properties, factoryID); + await setDeliveryTarget(level.kind, levelKey(level), level.kind === "busy" ? "SessionBusy" : "SessionIdle", properties, factoryID); } function postPrompt(root, prompt) { @@ -188,6 +217,18 @@ async function setupOpenCode2Tui(ctx) { } const root = rootOf(sessionID); const isOwned = memory.owned.includes(root); + if (sessionID === root && (isOwned || currentRoute() === root)) { + if (event.type === "session.execution.started" || event.type === "session.execution.succeeded" || event.type === "session.execution.failed" || event.type === "session.execution.interrupted") { + const errorName = event.type === "session.execution.failed" + ? (typeof properties.error?.type === "string" && properties.error.type) || "UnknownError" + : event.type === "session.execution.interrupted" && properties.reason === "user" ? "MessageAbortedError" : ""; + // A hot reload keeps the terminal verdict; only this root's next turn replaces it. + setMemory((draft) => { + draft.endings = (draft.endings || []).filter(([id]) => id !== root); + if (errorName) draft.endings = [...draft.endings, [root, errorName, event.created ?? data.get?.(root)?.time?.idle]].slice(-TUI_EARLY_ROOTS_MAX); + }); + } + } if (event.type === "session.inbox.enqueued") { // Why TUI-only: the server takes the prompt from session.hook("prompt"), which a TUI lacks. const item = properties.item; diff --git a/src/shared/agent-hook-listener/providers/opencode-family-events.ts b/src/shared/agent-hook-listener/providers/opencode-family-events.ts index e030276b08b..ba64837e42b 100644 --- a/src/shared/agent-hook-listener/providers/opencode-family-events.ts +++ b/src/shared/agent-hook-listener/providers/opencode-family-events.ts @@ -1,3 +1,4 @@ +import { continueMainAgentStatus } from '../../agent-lead-status-fold' import { normalizeAgentStatusPayload, type ParsedAgentStatusPayload @@ -41,6 +42,28 @@ export function normalizeOpenCodeFamilyEvent( } ) + const rootState = hookPayload.root_state + const errorName = hookPayload.root_turn_error_name + const mainAgent = + (source === 'opencode' || source === 'opencode2') && + (rootState === 'working' || rootState === 'waiting' || rootState === 'done') + ? continueMainAgentStatus( + eventName === 'SessionStart' + ? undefined + : state.lastStatusByPaneKey.get(paneKey)?.payload.mainAgent, + { + state: rootState, + outcome: + rootState === 'done' && typeof errorName === 'string' && errorName + ? errorName === 'MessageAbortedError' + ? 'cancellation' + : 'failure' + : undefined + }, + Date.now() + ) + : undefined + return normalizeAgentStatusPayload({ state: stateName, prompt: resolvePrompt(state, paneKey, promptText, { @@ -55,6 +78,8 @@ export function normalizeOpenCodeFamilyEvent( sessionBoundary: (source === 'opencode' || source === 'opencode2') && eventName === 'SessionStart' ? true - : undefined + : undefined, + ...(mainAgent ? { mainAgent } : {}), + ...(stateName === 'done' && mainAgent?.outcome === 'cancellation' ? { interrupted: true } : {}) }) } diff --git a/src/shared/agent-hook-listener/providers/opencode-family-tool-fields.ts b/src/shared/agent-hook-listener/providers/opencode-family-tool-fields.ts index 4236cace716..b28aa552d76 100644 --- a/src/shared/agent-hook-listener/providers/opencode-family-tool-fields.ts +++ b/src/shared/agent-hook-listener/providers/opencode-family-tool-fields.ts @@ -63,10 +63,13 @@ export function extractOpenCodeToolFields( (pattern): pattern is string => typeof pattern === 'string' && pattern.length > 0 ) : [] + const toolName = readString(hookPayload, 'permission') + const toolInput = metadataInput ?? (patterns.length > 0 ? patterns.join(', ') : undefined) return toolUpdate( { - toolName: readString(hookPayload, 'permission'), - toolInput: metadataInput ?? (patterns.length > 0 ? patterns.join(', ') : undefined) + toolName, + toolInput, + interactivePrompt: deriveInteractivePrompt(toolName, { command: toolInput }, eventName) }, { hasToolInputField: hasAnyOwnField(hookPayload, ['metadata', 'patterns']) } ) diff --git a/src/shared/opencode-permission-status.test.ts b/src/shared/opencode-permission-status.test.ts index 31316e30391..5d56134681b 100644 --- a/src/shared/opencode-permission-status.test.ts +++ b/src/shared/opencode-permission-status.test.ts @@ -27,7 +27,7 @@ describe('OpenCode-family permission request status', () => { state = createHookListenerState() }) - const SOURCES = ['opencode', 'mimo-code'] as const + const SOURCES = ['opencode', 'opencode2', 'mimo-code'] as const function permissionEvent( source: (typeof SOURCES)[number], @@ -87,6 +87,21 @@ describe('OpenCode-family permission request status', () => { expect(event?.payload.toolInput).toBe('rm -rf build/') }) + it.each(SOURCES)('carries the real v2 shell request as an approval card for %s', (source) => { + const event = permissionEvent(source, { + id: 'per_native', + sessionID: 'ses_native', + permission: 'shell', + patterns: ['printf PERMISSION_NATIVE_OK'], + source: { type: 'tool', messageID: 'msg_native', id: 'functions.shell:0' } + }) + expect(event?.payload.interactivePrompt).toBe( + JSON.stringify({ + approval: { tool: 'shell', summary: 'printf PERMISSION_NATIVE_OK' } + }) + ) + }) + it.each(SOURCES)('names the edited file for %s', (source) => { // Captured verbatim from opencode 1.18.18 — an `edit` request keys its path as // `filepath` (one word) and ships the whole patch alongside it. @@ -189,6 +204,7 @@ describe('OpenCode-family permission request status', () => { expect(resumed?.payload.state).toBe('working') expect(resumed?.payload.toolName).toBeUndefined() expect(resumed?.payload.toolInput).toBeUndefined() + expect(resumed?.payload.interactivePrompt).toBeUndefined() }) it.each(SOURCES)('retires the permission when the session goes idle for %s', (source) => { @@ -198,6 +214,7 @@ describe('OpenCode-family permission request status', () => { expect(idle?.payload.state).toBe('done') expect(idle?.payload.toolName).toBeUndefined() expect(idle?.payload.toolInput).toBeUndefined() + expect(idle?.payload.interactivePrompt).toBeUndefined() }) it.each(SOURCES)( From 76b1a90ff69544a96eef19997c72abf58fe7a5df Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 2 Oct 2026 05:05:43 -0700 Subject: [PATCH 17/25] chore(deps): update reviewed dependencies across Orca (#24561) * chore(deps): update reviewed desktop dependencies and tooling * chore(deps): update compatible mobile packages and Fastlane * chore(deps): update cloud transports and enforce release age * chore(deps): patch documentation dependencies and record review * chore: remove dependency review reports * test(linear): smoke-load resolved SDK through CommonJS loader * fix(deps): keep native rebuilds from reinstalling addon dependencies * fix(native): invoke installed node-gyp directly for Node rebuilds * test(cloud): exclude observer probes from row-lock timing budget * test(mobile): preserve the CSS writer receiver in viewport spy * test(native): remove obsolete batch-shim fixture exception * Stream native rebuild output through the process wrapper --- .github/workflows/docs.yml | 4 +- .github/workflows/unit-tests.yml | 4 +- cloud/apps/push/package.json | 8 +- cloud/apps/relay-fence-broker/package.json | 8 +- cloud/apps/relay-ops/package.json | 8 +- cloud/apps/relay/package.json | 12 +- ...al-rehome-target-row-lock-postgres.test.ts | 18 +- cloud/package.json | 4 +- cloud/packages/postgres-schema/package.json | 2 +- cloud/packages/push-contract/package.json | 2 +- cloud/packages/relay-contract/package.json | 2 +- cloud/pnpm-lock.yaml | 503 +-- cloud/pnpm-workspace.yaml | 1 + .../docker/headless-pairing/Dockerfile.build | 2 +- config/scripts/check-react-doctor-changed.mjs | 2 +- config/scripts/ensure-native-runtime.mjs | 37 +- config/scripts/ensure-native-runtime.test.mjs | 169 +- config/scripts/linear-sdk-runtime.test.mjs | 78 + ...tched-dependencies-frozen-install.test.mjs | 5 + .../scripts/pr-workflow-parallelism.test.mjs | 2 +- .../windows-cmd-shim-spawn-boundary.test.mjs | 1 - .../windows-process-tree-gyp-rebuild.mjs | 12 +- .../windows-process-tree-gyp-rebuild.test.mjs | 8 + docs/site/.gitignore | 2 +- docs/site/.npmrc | 1 + docs/site/package.json | 32 +- docs/site/pnpm-lock.yaml | 1562 +++---- .../site/src/components/layout/DocsFooter.tsx | 4 +- docs/site/tests/docs-package.test.mjs | 2 +- mobile/Gemfile | 2 +- mobile/Gemfile.lock | 28 +- mobile/package.json | 30 +- .../packages/expo-two-way-audio/package.json | 6 +- mobile/pnpm-lock.yaml | 2278 +++++----- mobile/pnpm-workspace.yaml | 3 + .../terminal/document/viewport-frame.test.ts | 13 +- .../terminal-web-document-mount.test.ts | 21 +- package.json | 62 +- pnpm-lock.yaml | 3860 +++++++++-------- pnpm-workspace.yaml | 9 +- .../claude-agent-sdk-contract-pins.test.ts | 2 +- .../claude/claude-structured-real-cli.test.ts | 12 +- ...rminal-ime-xterm-preedit-cell-grid.test.ts | 18 +- 43 files changed, 4564 insertions(+), 4275 deletions(-) create mode 100644 config/scripts/linear-sdk-runtime.test.mjs create mode 100644 docs/site/.npmrc diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 69a487607b0..7830df9eacf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,7 +49,7 @@ jobs: # v5 avoids the v6 bootstrap/shim regression when pinning pnpm 10. uses: pnpm/action-setup@v5 with: - version: 10.24.0 + version: 10.34.6 package_json_file: docs/site/package.json run_install: false @@ -224,7 +224,7 @@ jobs: # v5 avoids the v6 bootstrap/shim regression when pinning pnpm 10. uses: pnpm/action-setup@v5 with: - version: 10.24.0 + version: 10.34.6 package_json_file: docs/site/package.json run_install: false diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 2e6f173acc4..fed0c4c31d1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -105,8 +105,8 @@ jobs: - name: Install relay integration dependencies working-directory: cloud run: | - npx --yes pnpm@10.24.0 --filter '@orca-cloud/relay...' install --frozen-lockfile --ignore-scripts - npx --yes pnpm@10.24.0 --filter '@orca-cloud/relay^...' build + npx --yes pnpm@10.34.6 --filter '@orca-cloud/relay...' install --frozen-lockfile --ignore-scripts + npx --yes pnpm@10.34.6 --filter '@orca-cloud/relay^...' build - name: Test relay integration contracts env: diff --git a/cloud/apps/push/package.json b/cloud/apps/push/package.json index b47964587ac..a4ba2462f5b 100644 --- a/cloud/apps/push/package.json +++ b/cloud/apps/push/package.json @@ -15,20 +15,20 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@hono/node-server": "^1.19.17", + "@hono/node-server": "^2.1.2", "@orca-cloud/postgres-schema": "workspace:*", "@orca-cloud/push-contract": "workspace:*", "google-auth-library": "^10.5.0", - "hono": "^4.13.7", + "hono": "^4.13.10", "pg": "^8.22.0", "pg-connection-string": "2.14.0", "tweetnacl": "^1.0.3", "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "@types/pg": "^8.20.0", - "tsx": "^4.21.0", + "tsx": "^4.23.15", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/apps/relay-fence-broker/package.json b/cloud/apps/relay-fence-broker/package.json index b14f162d77d..6e95ece0581 100644 --- a/cloud/apps/relay-fence-broker/package.json +++ b/cloud/apps/relay-fence-broker/package.json @@ -14,13 +14,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@hono/node-server": "^1.19.17", - "hono": "^4.13.7", + "@hono/node-server": "^2.1.2", + "hono": "^4.13.10", "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", - "tsx": "^4.21.0", + "@types/node": "^24.19.0", + "tsx": "^4.23.15", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/apps/relay-ops/package.json b/cloud/apps/relay-ops/package.json index 5865d6c441d..d3961198665 100644 --- a/cloud/apps/relay-ops/package.json +++ b/cloud/apps/relay-ops/package.json @@ -17,13 +17,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@hono/node-server": "^1.19.17", - "hono": "^4.13.7", + "@hono/node-server": "^2.1.2", + "hono": "^4.13.10", "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", - "tsx": "^4.21.0", + "@types/node": "^24.19.0", + "tsx": "^4.23.15", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/apps/relay/package.json b/cloud/apps/relay/package.json index e9624bc3080..0a85bc9343e 100644 --- a/cloud/apps/relay/package.json +++ b/cloud/apps/relay/package.json @@ -15,21 +15,21 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@hono/node-server": "^1.19.17", + "@hono/node-server": "^2.1.2", "@orca-cloud/postgres-schema": "workspace:*", "@orca-cloud/relay-contract": "workspace:*", - "hono": "^4.13.7", - "jose": "^6.1.3", + "hono": "^4.13.10", + "jose": "^6.2.12", "pg": "^8.22.0", "tweetnacl": "^1.0.3", - "ws": "^8.21.3", + "ws": "^8.22.0", "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "@types/pg": "^8.20.0", "@types/ws": "^8.18.1", - "tsx": "^4.21.0", + "tsx": "^4.23.15", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/apps/relay/src/regional-rehome-target-row-lock-postgres.test.ts b/cloud/apps/relay/src/regional-rehome-target-row-lock-postgres.test.ts index 314b758b17f..c61c6b3ff0f 100644 --- a/cloud/apps/relay/src/regional-rehome-target-row-lock-postgres.test.ts +++ b/cloud/apps/relay/src/regional-rehome-target-row-lock-postgres.test.ts @@ -32,7 +32,7 @@ const CELL_TABLES = [ // The target-row statement locks exactly these, held from it to COMMIT. const TARGET_LOCKED = ['target:relay_cells', 'target:relay_cell_admission'] -type Trip = { sql: string; lockable: Record } +type Trip = { sql: string; lockable: Record; probeMs: number } type DelayControl = StatementDelay & { beforeTrip: (sql: string) => Promise } @@ -159,11 +159,12 @@ describePostgres('PostgreSQL regional rehome target-row lock', () => { bystander: context.bystander.id } control.beforeTrip = async (sql) => { + const probeStartedAt = performance.now() const state: Record = {} for (const [role, cellId] of Object.entries(probed)) { for (const table of CELL_TABLES) state[`${role}:${table}`] = await lockable(table, cellId) } - trips.push({ sql, lockable: state }) + trips.push({ sql, lockable: state, probeMs: performance.now() - probeStartedAt }) } consumeRelayCellInventoryHold(delayed) control.enabled = true @@ -188,13 +189,22 @@ describePostgres('PostgreSQL regional rehome target-row lock', () => { ]) } const counts = consumeRelayCellInventoryHold(delayed) + const commitProbeMs = trips.at(-1)!.probeMs console.info( - JSON.stringify({ event: 'rehome_target_row_hold', trips: trips.length, ...counts }) + JSON.stringify({ + event: 'rehome_target_row_hold', + trips: trips.length, + commitProbeMs, + ...counts + }) ) expect(counts.rehomeTargetRowHolds).toBe(1) expect(counts.cellInventoryHoldMaxSite).toBe('rehome-target-row') expect(counts.rehomeTargetRowHoldMsMax).toBeGreaterThanOrEqual(STATEMENT_DELAY_MS) - expect(counts.rehomeTargetRowHoldMsMax).toBeLessThanOrEqual(2 * STATEMENT_DELAY_MS) + // The COMMIT observer probes run under the lock, but are absent in production. + expect(counts.rehomeTargetRowHoldMsMax - commitProbeMs).toBeLessThanOrEqual( + 2 * STATEMENT_DELAY_MS + ) expect(await reservedRequests(context.target.id)).toBe(context.targetReservedBefore + 2) }) diff --git a/cloud/package.json b/cloud/package.json index 1770e91a7b1..e2c02ba2416 100644 --- a/cloud/package.json +++ b/cloud/package.json @@ -2,7 +2,7 @@ "name": "orca-cloud", "private": true, "version": "0.0.0", - "packageManager": "pnpm@10.24.0", + "packageManager": "pnpm@10.34.6", "engines": { "node": ">=24 <27", "pnpm": ">=10" @@ -26,7 +26,7 @@ "typecheck": "pnpm -r typecheck" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "tsx": "^4.23.15", "typescript": "^5.9.3", "vitest": "^4.1.11" diff --git a/cloud/packages/postgres-schema/package.json b/cloud/packages/postgres-schema/package.json index 05cd4bd7221..594c799a5b5 100644 --- a/cloud/packages/postgres-schema/package.json +++ b/cloud/packages/postgres-schema/package.json @@ -13,7 +13,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/packages/push-contract/package.json b/cloud/packages/push-contract/package.json index 75698aa46f9..19f4941ba4a 100644 --- a/cloud/packages/push-contract/package.json +++ b/cloud/packages/push-contract/package.json @@ -16,7 +16,7 @@ "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/packages/relay-contract/package.json b/cloud/packages/relay-contract/package.json index 976840da87d..5aa329a3d81 100644 --- a/cloud/packages/relay-contract/package.json +++ b/cloud/packages/relay-contract/package.json @@ -16,7 +16,7 @@ "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^24.10.0", + "@types/node": "^24.19.0", "typescript": "^5.9.3", "vitest": "^4.1.11" } diff --git a/cloud/pnpm-lock.yaml b/cloud/pnpm-lock.yaml index 6042ee85cd4..c688889cb50 100644 --- a/cloud/pnpm-lock.yaml +++ b/cloud/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 tsx: specifier: ^4.23.15 version: 4.23.15 @@ -19,13 +19,13 @@ importers: version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) apps/push: dependencies: '@hono/node-server': - specifier: ^1.19.17 - version: 1.19.17(hono@4.13.7) + specifier: ^2.1.2 + version: 2.1.2(hono@4.13.10) '@orca-cloud/postgres-schema': specifier: workspace:* version: link:../../packages/postgres-schema @@ -36,8 +36,8 @@ importers: specifier: ^10.5.0 version: 10.9.1 hono: - specifier: ^4.13.7 - version: 4.13.7 + specifier: ^4.13.10 + version: 4.13.10 pg: specifier: ^8.22.0 version: 8.22.0 @@ -52,26 +52,26 @@ importers: version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 '@types/pg': specifier: ^8.20.0 version: 8.20.0 tsx: - specifier: ^4.21.0 - version: 4.22.4 + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) apps/relay: dependencies: '@hono/node-server': - specifier: ^1.19.17 - version: 1.19.17(hono@4.13.7) + specifier: ^2.1.2 + version: 2.1.2(hono@4.13.10) '@orca-cloud/postgres-schema': specifier: workspace:* version: link:../../packages/postgres-schema @@ -79,11 +79,11 @@ importers: specifier: workspace:* version: link:../../packages/relay-contract hono: - specifier: ^4.13.7 - version: 4.13.7 + specifier: ^4.13.10 + version: 4.13.10 jose: - specifier: ^6.1.3 - version: 6.2.3 + specifier: ^6.2.12 + version: 6.2.12 pg: specifier: ^8.22.0 version: 8.22.0 @@ -91,15 +91,15 @@ importers: specifier: ^1.0.3 version: 1.0.3 ws: - specifier: ^8.21.3 - version: 8.21.3 + specifier: ^8.22.0 + version: 8.22.0 zod: specifier: ^3.25.76 version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 '@types/pg': specifier: ^8.20.0 version: 8.20.0 @@ -107,76 +107,76 @@ importers: specifier: ^8.18.1 version: 8.18.1 tsx: - specifier: ^4.21.0 - version: 4.22.4 + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) apps/relay-fence-broker: dependencies: '@hono/node-server': - specifier: ^1.19.17 - version: 1.19.17(hono@4.13.7) + specifier: ^2.1.2 + version: 2.1.2(hono@4.13.10) hono: - specifier: ^4.13.7 - version: 4.13.7 + specifier: ^4.13.10 + version: 4.13.10 zod: specifier: ^3.25.76 version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 tsx: - specifier: ^4.21.0 - version: 4.22.4 + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) apps/relay-ops: dependencies: '@hono/node-server': - specifier: ^1.19.17 - version: 1.19.17(hono@4.13.7) + specifier: ^2.1.2 + version: 2.1.2(hono@4.13.10) hono: - specifier: ^4.13.7 - version: 4.13.7 + specifier: ^4.13.10 + version: 4.13.10 zod: specifier: ^3.25.76 version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 tsx: - specifier: ^4.21.0 - version: 4.22.4 + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) packages/postgres-schema: devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) packages/push-contract: dependencies: @@ -185,14 +185,14 @@ importers: version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) packages/relay-contract: dependencies: @@ -201,14 +201,14 @@ importers: version: 3.25.76 devDependencies: '@types/node': - specifier: ^24.10.0 - version: 24.13.2 + specifier: ^24.19.0 + version: 24.19.0 typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)) + version: 4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) packages: @@ -221,321 +221,165 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.2': resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.2': resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.2': resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.2': resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.2': resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.2': resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.2': resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.2': resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.2': resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.2': resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.2': resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.2': resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.2': resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.2': resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.2': resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.2': resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.2': resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.2': resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.2': resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.2': resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.2': resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.2': resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.2': resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.2': resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.2': resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@hono/node-server@1.19.17': - resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} - engines: {node: '>=18.14.1'} + '@hono/node-server@2.1.2': + resolution: {integrity: sha512-w0tqJ/ilmggKokOSvbp9y+F91Y4ROTyCTL/8EKj9B4qOhOZzNm/tyKElzxrz+9+4fJKQrBSaahIuCGRye5RJYg==} + engines: {node: '>=20'} peerDependencies: hono: ^4 @@ -587,36 +431,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.3': resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.3': resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.3': resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.3': resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.3': resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.3': resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} @@ -659,8 +509,8 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/node@24.13.2': - resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + '@types/node@24.19.0': + resolution: {integrity: sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==} '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} @@ -744,11 +594,6 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -802,16 +647,16 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} - hono@4.13.7: - resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} + hono@4.13.10: + resolution: {integrity: sha512-dQuLsa5oO+47QVMVMaaD9cIv8ctmVtK1iRvwWngkfloFJMeFeuoUFDswIqZGxmGX3hrRzREgEArjkK9OgsQEhA==} engines: {node: '>=16.9.0'} https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -857,24 +702,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -1027,11 +876,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} - engines: {node: '>=18.0.0'} - hasBin: true - tsx@4.23.15: resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} @@ -1045,8 +889,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} vite@8.0.16: resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} @@ -1141,8 +985,8 @@ packages: engines: {node: '>=8'} hasBin: true - ws@8.21.3: - resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1178,165 +1022,87 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.28.1': - optional: true - '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': - optional: true - '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': - optional: true - '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': - optional: true - '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': - optional: true - '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': - optional: true - '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': - optional: true - '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': - optional: true - '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': - optional: true - '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': - optional: true - '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': - optional: true - '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': - optional: true - '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': - optional: true - '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': - optional: true - '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': - optional: true - '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': - optional: true - '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': - optional: true - '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': - optional: true - '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': - optional: true - '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': - optional: true - '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': - optional: true - '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': - optional: true - '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': - optional: true - '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': - optional: true - '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': - optional: true - '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': - optional: true - '@esbuild/win32-x64@0.28.2': optional: true - '@hono/node-server@1.19.17(hono@4.13.7)': + '@hono/node-server@2.1.2(hono@4.13.10)': dependencies: - hono: 4.13.7 + hono: 4.13.10 '@jridgewell/sourcemap-codec@1.5.5': {} @@ -1416,19 +1182,19 @@ snapshots: '@types/estree@1.0.9': {} - '@types/node@24.13.2': + '@types/node@24.19.0': dependencies: - undici-types: 7.18.2 + undici-types: 7.24.6 '@types/pg@8.20.0': dependencies: - '@types/node': 24.13.2 + '@types/node': 24.19.0 pg-protocol: 1.15.0 pg-types: 2.2.0 '@types/ws@8.18.1': dependencies: - '@types/node': 24.13.2 + '@types/node': 24.19.0 '@vitest/expect@4.1.11': dependencies: @@ -1439,21 +1205,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.11(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4))': + '@vitest/mocker@4.1.11(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4) - - '@vitest/mocker@4.1.11(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15) + vite: 8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15) '@vitest/pretty-format@4.1.11': dependencies: @@ -1507,35 +1265,6 @@ snapshots: es-module-lexer@2.1.0: {} - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -1618,7 +1347,7 @@ snapshots: google-logging-utils@1.1.3: {} - hono@4.13.7: {} + hono@4.13.10: {} https-proxy-agent@7.0.6: dependencies: @@ -1627,7 +1356,7 @@ snapshots: transitivePeerDependencies: - supports-color - jose@6.2.3: {} + jose@6.2.12: {} json-bigint@1.0.0: dependencies: @@ -1817,12 +1546,6 @@ snapshots: tslib@2.8.1: optional: true - tsx@4.22.4: - dependencies: - esbuild: 0.28.1 - optionalDependencies: - fsevents: 2.3.3 - tsx@4.23.15: dependencies: esbuild: 0.28.2 @@ -1833,9 +1556,9 @@ snapshots: typescript@5.9.3: {} - undici-types@7.18.2: {} + undici-types@7.24.6: {} - vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4): + vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -1843,28 +1566,15 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.13.2 - esbuild: 0.28.2 - fsevents: 2.3.3 - tsx: 4.22.4 - - vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.0.3 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.19.0 esbuild: 0.28.2 fsevents: 2.3.3 tsx: 4.23.15 - vitest@4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)): + vitest@4.1.11(@types/node@24.19.0)(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4)) + '@vitest/mocker': 4.1.11(vite@8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -1881,37 +1591,10 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.22.4) + vite: 8.0.16(@types/node@24.19.0)(esbuild@0.28.2)(tsx@4.23.15) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.13.2 - transitivePeerDependencies: - - msw - - vitest@4.1.11(@types/node@24.13.2)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)): - dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.3 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.2)(tsx@4.23.15) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.13.2 + '@types/node': 24.19.0 transitivePeerDependencies: - msw @@ -1922,7 +1605,7 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - ws@8.21.3: {} + ws@8.22.0: {} xtend@4.0.2: {} diff --git a/cloud/pnpm-workspace.yaml b/cloud/pnpm-workspace.yaml index cc61e60464c..97bf7cd8c2e 100644 --- a/cloud/pnpm-workspace.yaml +++ b/cloud/pnpm-workspace.yaml @@ -2,3 +2,4 @@ packages: - apps/* - packages/* +minimumReleaseAge: 4320 diff --git a/config/docker/headless-pairing/Dockerfile.build b/config/docker/headless-pairing/Dockerfile.build index 19f76290c75..0300d8a82d4 100644 --- a/config/docker/headless-pairing/Dockerfile.build +++ b/config/docker/headless-pairing/Dockerfile.build @@ -26,7 +26,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* RUN corepack enable \ - && corepack prepare pnpm@12.0.0 --activate \ + && corepack prepare pnpm@12.8.1 --activate \ && pnpm --version WORKDIR /workspace diff --git a/config/scripts/check-react-doctor-changed.mjs b/config/scripts/check-react-doctor-changed.mjs index 743a64eee04..92a62b461f1 100644 --- a/config/scripts/check-react-doctor-changed.mjs +++ b/config/scripts/check-react-doctor-changed.mjs @@ -26,7 +26,7 @@ const result = spawnSync( [ ...prefixArgs, 'dlx', - 'react-doctor@0.9.1', + 'react-doctor@0.9.14', '.', '--yes', '--scope', diff --git a/config/scripts/ensure-native-runtime.mjs b/config/scripts/ensure-native-runtime.mjs index c93b6a19d62..3d701b23944 100644 --- a/config/scripts/ensure-native-runtime.mjs +++ b/config/scripts/ensure-native-runtime.mjs @@ -8,9 +8,11 @@ import { basename, dirname, resolve } from 'node:path' import { ensureWindowsProcessTreeCommandLinePatch, inspectWindowsProcessTreeAddon, + nodeGypRebuildInvocation, stageWindowsProcessTreeNodeAddonApiHeaders, windowsProcessTreeAddonPath } from './windows-process-tree-gyp-rebuild.mjs' +import { describeProcessFailure, runProcessSync } from './script-child-process.mjs' const require = createRequire(import.meta.url) const { assertNodePtyJobOwnership, nodePtyAddonPath } = require('./node-pty-job-ownership.cjs') @@ -400,33 +402,38 @@ function rebuildNodeRuntimeModules(moduleNames) { moduleDir = realpathSync(moduleDir) } console.warn(`[native-runtime] Rebuilding ${moduleName} with node-gyp.`) - runPnpm(['exec', 'node-gyp', 'rebuild'], { cwd: moduleDir }) + // pnpm exec inside an installed addon cannot discover the root build tool. + runNodeGyp( + nodeGypRebuildInvocation( + process.arch, + moduleDir, + process.env.npm_config_node_gyp || undefined + ) + ) if (moduleName === 'node-pty' && process.platform === 'win32') { runNodeScript([resolve(moduleDir, 'scripts', 'post-install.js')]) } } } -function runPnpm(args, { cwd = projectDir } = {}) { - // cmd.exe resolves both Corepack's pnpm.cmd and pnpm 12's native pnpm.exe. - const command = 'pnpm' +function runNodeGyp({ args, cwd }) { const env = - process.platform === 'linux' && args.includes('node-gyp') + process.platform === 'linux' ? { ...process.env, CXXFLAGS: `${process.env.CXXFLAGS ?? ''} -std=gnu++2a`.trim() } : process.env - const result = spawnSync(command, args, { + const result = runProcessSync({ + program: process.execPath, + args, cwd, + env, stdio: 'inherit', - shell: process.platform === 'win32', - env + timeoutMs: 300_000 }) - - if (result.error || result.status !== 0) { - console.error(`[native-runtime] ${command} ${args.join(' ')} failed in ${cwd}.`) - if (result.error) { - console.error(formatError(result.error)) - } - process.exit(result.status ?? 1) + if (result.code !== 0) { + console.error( + `[native-runtime] node-gyp rebuild failed in ${cwd}: ${describeProcessFailure(result)}` + ) + process.exit(result.code ?? 1) } } diff --git a/config/scripts/ensure-native-runtime.test.mjs b/config/scripts/ensure-native-runtime.test.mjs index 47fe435edcc..4d6e022b185 100644 --- a/config/scripts/ensure-native-runtime.test.mjs +++ b/config/scripts/ensure-native-runtime.test.mjs @@ -1,7 +1,7 @@ import { spawnSync } from 'node:child_process' import { - chmodSync, copyFileSync, + existsSync, mkdirSync, mkdtempSync, readFileSync, @@ -9,10 +9,18 @@ import { writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' -import { delimiter, join } from 'node:path' +import { isAbsolute, join, parse } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' +import { + DEFAULT_MAX_OUTPUT_BYTES, + runProcessSync +} from '../../src/shared/child-process/run-process.ts' +import { resolveCliCommand } from '../../src/shared/node-cli-command-resolution.ts' +import { removeTreeSync } from '../../src/shared/windows-transient-lock-removal.ts' +import { resolvePnpmCliInvocation } from './pnpm-cli-invocation.mjs' import { copyScriptWithLocalModules } from './script-module-dependencies.mjs' +import { nodeGypRebuildInvocation } from './windows-process-tree-gyp-rebuild.mjs' const sourceScriptPath = fileURLToPath(new URL('./ensure-native-runtime.mjs', import.meta.url)) // The import walk sees `from './x.mjs'` only, so the createRequire'd CJS @@ -30,23 +38,28 @@ describe('ensure-native-runtime', () => { const scriptPath = join(projectDir, 'config', 'scripts', 'ensure-native-runtime.mjs') const logPath = join(projectDir, 'native-runtime.log') const markerPath = join(projectDir, 'rebuilt.marker') - const binDir = join(projectDir, 'bin') writeFakeNativeModules(projectDir) writeNodePtyPatchFile(projectDir) - writeFakePnpm(binDir) + writeFakeNodeGyp(projectDir) + const verboseOutputBytes = DEFAULT_MAX_OUTPUT_BYTES + 1024 * 1024 const result = spawnSync(process.execPath, [scriptPath, '--runtime=node'], { cwd: projectDir, encoding: 'utf8', - env: envWithPrependedPath(binDir, { + maxBuffer: DEFAULT_MAX_OUTPUT_BYTES * 4, + env: envForNativeFixture(projectDir, { ORCA_NATIVE_TEST_LOG: logPath, - ORCA_NATIVE_TEST_MARKER: markerPath + ORCA_NATIVE_TEST_MARKER: markerPath, + ORCA_NATIVE_TEST_VERBOSE_OUTPUT_BYTES: String(verboseOutputBytes) }) }) expect(result.status, result.stderr).toBe(0) + expect(result.stdout.indexOf('node-gyp stdout complete\n')).toBe(verboseOutputBytes) + expect(/^x+$/.test(result.stdout.slice(0, verboseOutputBytes))).toBe(true) + expect(result.stderr).toContain('node-gyp stderr complete\n') const log = readFileSync(logPath, 'utf8') - expect(log).toContain('pnpm exec node-gyp rebuild\n') + expect(log).toContain(`node-gyp rebuild --arch=${process.arch}\n`) expect(log).toContain(join('node_modules', 'node-pty')) if (process.platform === 'linux') { expect(log).toMatch(/^cxxflags=(?:.*\s)?-std=gnu\+\+2a$/m) @@ -69,15 +82,14 @@ describe('ensure-native-runtime', () => { const scriptPath = join(projectDir, 'config', 'scripts', 'ensure-native-runtime.mjs') const logPath = join(projectDir, 'native-runtime.log') const markerPath = join(projectDir, 'rebuilt.marker') - const binDir = join(projectDir, 'bin') writeFakeNativeModules(projectDir, { windowsRegistryRequiresMarker: true }) writeNodePtyPatchFile(projectDir) - writeFakePnpm(binDir) + writeFakeNodeGyp(projectDir) const result = spawnSync(process.execPath, [scriptPath, '--runtime=node'], { cwd: projectDir, encoding: 'utf8', - env: envWithPrependedPath(binDir, { + env: envForNativeFixture(projectDir, { ORCA_NATIVE_TEST_LOG: logPath, ORCA_NATIVE_TEST_MARKER: markerPath }) @@ -85,7 +97,9 @@ describe('ensure-native-runtime', () => { expect(result.status, result.stderr).toBe(0) const log = readFileSync(logPath, 'utf8') - expect(log.match(/pnpm exec node-gyp rebuild\n/g)).toHaveLength(2) + expect( + log.split('\n').filter((line) => line === `node-gyp rebuild --arch=${process.arch}`) + ).toHaveLength(2) expect(log).toContain(join('node_modules', 'node-pty')) expect(log).toContain(join('node_modules', '@orca', 'windows-registry')) } finally { @@ -103,15 +117,14 @@ describe('ensure-native-runtime', () => { const scriptPath = join(projectDir, 'config', 'scripts', 'ensure-native-runtime.mjs') const logPath = join(projectDir, 'native-runtime.log') const markerPath = join(projectDir, 'rebuilt.marker') - const binDir = join(projectDir, 'bin') writeLoadableNativeModules(projectDir) writeNodePtyPatchFile(projectDir) - writeFakePnpm(binDir) + writeFakeNodeGyp(projectDir) const result = spawnSync(process.execPath, [scriptPath, '--runtime=node'], { cwd: projectDir, encoding: 'utf8', - env: envWithPrependedPath(binDir, { + env: envForNativeFixture(projectDir, { ORCA_NATIVE_TEST_LOG: logPath, ORCA_NATIVE_TEST_MARKER: markerPath }) @@ -121,7 +134,7 @@ describe('ensure-native-runtime', () => { expect(result.stderr).toContain( 'Patched node-pty build artifacts are missing; rebuilding native deps.' ) - expect(readFileSync(logPath, 'utf8')).toContain('pnpm exec node-gyp rebuild\n') + expect(readFileSync(logPath, 'utf8')).toContain(`node-gyp rebuild --arch=${process.arch}\n`) } finally { rmSync(projectDir, { recursive: true, force: true }) } @@ -137,16 +150,15 @@ describe('ensure-native-runtime', () => { const scriptPath = join(projectDir, 'config', 'scripts', 'ensure-native-runtime.mjs') const logPath = join(projectDir, 'native-runtime.log') const markerPath = join(projectDir, 'rebuilt.marker') - const binDir = join(projectDir, 'bin') writeLoadableNativeModules(projectDir) writeNodePtyPatchFile(projectDir) writePatchedNodePtyBuildArtifacts(projectDir) - writeFakePnpm(binDir) + writeFakeNodeGyp(projectDir) const result = spawnSync(process.execPath, [scriptPath, '--runtime=node'], { cwd: projectDir, encoding: 'utf8', - env: envWithPrependedPath(binDir, { + env: envForNativeFixture(projectDir, { ORCA_NATIVE_TEST_LOG: logPath, ORCA_NATIVE_TEST_MARKER: markerPath }) @@ -154,7 +166,7 @@ describe('ensure-native-runtime', () => { expect(result.status, result.stderr).toBe(0) expect(result.stderr).toContain("expected build/Release so Orca's node-pty patch is active") - expect(readFileSync(logPath, 'utf8')).toContain('pnpm exec node-gyp rebuild\n') + expect(readFileSync(logPath, 'utf8')).toContain(`node-gyp rebuild --arch=${process.arch}\n`) } finally { rmSync(projectDir, { recursive: true, force: true }) } @@ -170,16 +182,15 @@ describe('ensure-native-runtime', () => { const scriptPath = join(projectDir, 'config', 'scripts', 'ensure-native-runtime.mjs') const logPath = join(projectDir, 'native-runtime.log') const markerPath = join(projectDir, 'rebuilt.marker') - const binDir = join(projectDir, 'bin') writeLoadableNativeModules(projectDir, { nativeDir: '../build/Release/' }) writeNodePtyPatchFile(projectDir) writePatchedNodePtyBuildArtifacts(projectDir) - writeFakePnpm(binDir) + writeFakeNodeGyp(projectDir) const result = spawnSync(process.execPath, [scriptPath, '--runtime=node'], { cwd: projectDir, encoding: 'utf8', - env: envWithPrependedPath(binDir, { + env: envForNativeFixture(projectDir, { ORCA_NATIVE_TEST_LOG: logPath, ORCA_NATIVE_TEST_MARKER: markerPath }) @@ -187,12 +198,64 @@ describe('ensure-native-runtime', () => { expect(result.status, result.stderr).toBe(0) expect(result.stderr).not.toContain('Patched node-pty build artifacts are missing') - expect(readFileSync(logPath, 'utf8')).not.toContain('pnpm exec node-gyp rebuild') + expect(readFileSync(logPath, 'utf8')).not.toContain('node-gyp rebuild') } finally { rmSync(projectDir, { recursive: true, force: true }) } } ) + it('finds the installed node-gyp entry from an addon without build tools on PATH', () => { + const { command, prefixArgs } = resolvePnpmCliInvocation() + const program = isAbsolute(command) ? command : resolveCliCommand(parse(command).name) + expect(isAbsolute(program), 'pnpm must be installed for the native rebuild contract').toBe(true) + const projectDir = mkTempProject() + try { + writeFakeNativeModules(projectDir) + const addonDir = join(projectDir, 'node_modules', 'node-pty') + const env = { ...process.env, ORCA_BACKGROUND_LAUNCH: '1' } + for (const key of Object.keys(env)) { + if (key.toLowerCase() === 'path') { + env[key] = '' + } + } + const oldInvocation = runProcessSync({ + program, + args: [ + ...prefixArgs, + '--config.verify-deps-before-run=false', + 'exec', + 'node-gyp', + '--version' + ], + cwd: addonDir, + env, + timeoutMs: 20_000 + }) + expect(oldInvocation.code).not.toBe(0) + expect(`${oldInvocation.stdout}\n${oldInvocation.stderr}`).toContain( + 'Command "node-gyp" not found' + ) + const { args, cwd } = nodeGypRebuildInvocation(process.arch, addonDir) + const installedInvocation = runProcessSync({ + program: process.execPath, + args: [args[0], '--version'], + cwd, + env, + timeoutMs: 20_000 + }) + expect( + installedInvocation.code, + `${installedInvocation.stdout}\n${installedInvocation.stderr}` + ).toBe(0) + const manifest = JSON.parse( + readFileSync(new URL('../../node_modules/node-gyp/package.json', import.meta.url), 'utf8') + ) + expect(installedInvocation.stdout.trim()).toBe(`v${manifest.version}`) + expect(existsSync(join(addonDir, 'pnpm-lock.yaml'))).toBe(false) + } finally { + removeTreeSync(projectDir) + } + }) }) function mkTempProject() { @@ -200,6 +263,10 @@ function mkTempProject() { // Walked, not listed: the script imports windows-process-tree-gyp-rebuild.mjs, and a fixture // missing it fails every case with a module-resolution error instead of the defect under test. copyScriptWithLocalModules(sourceScriptPath, join(projectDir, 'config', 'scripts')) + writeFileSync( + join(projectDir, 'config', 'scripts', 'script-child-process.mjs'), + `export { describeProcessFailure, runProcessSync } from ${JSON.stringify(new URL('./script-child-process.mjs', import.meta.url).href)}\n` + ) for (const name of REQUIRED_CJS_SIBLINGS) { copyFileSync( fileURLToPath(new URL(`./${name}`, import.meta.url)), @@ -209,15 +276,11 @@ function mkTempProject() { return projectDir } -function envWithPrependedPath(binDir, extraEnv) { - const pathKey = - process.platform === 'win32' - ? (Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ?? 'Path') - : 'PATH' +function envForNativeFixture(projectDir, extraEnv) { return { ...process.env, ...extraEnv, - [pathKey]: `${binDir}${delimiter}${process.env[pathKey] ?? ''}` + npm_config_node_gyp: join(projectDir, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js') } } @@ -258,6 +321,11 @@ exports.loadNativeModule = function loadNativeModule(nativeName) { ` ) writeFakeWindowsRegistry(projectDir, { requiresMarker: windowsRegistryRequiresMarker }) + if (process.platform === 'win32') { + const buildDir = join(nodePtyDir, 'build', 'Release') + mkdirSync(buildDir, { recursive: true }) + writeFileSync(join(buildDir, 'conpty.node'), Buffer.from('msys-2.0.dll', 'utf16le')) + } } function writeLoadableNativeModules(projectDir, { nativeDir = null } = {}) { @@ -314,7 +382,10 @@ function writeFakeWindowsRegistry(projectDir, { requiresMarker = false } = {}) { ) const processTreeDir = join(projectDir, 'node_modules', '@vscode', 'windows-process-tree') mkdirSync(processTreeDir, { recursive: true }) - writeFileSync(join(processTreeDir, 'index.js'), 'module.exports = {}\n') + writeFileSync( + join(processTreeDir, 'index.js'), + 'exports.supportedProcessDataFlags = 4; exports.getProcessCreationTime = () => 1\n' + ) } function writeNodePtyPatchFile(projectDir) { @@ -338,33 +409,25 @@ function writePatchedNodePtyBuildArtifacts(projectDir) { } } -function writeFakePnpm(binDir) { - mkdirSync(binDir, { recursive: true }) - const shimPath = join(binDir, 'pnpm-shim.cjs') +function writeFakeNodeGyp(projectDir) { + const toolDir = join(projectDir, 'node_modules', 'node-gyp', 'bin') + mkdirSync(toolDir, { recursive: true }) writeFileSync( - shimPath, + join(toolDir, 'node-gyp.js'), ` -const { appendFileSync, writeFileSync } = require('node:fs') - -appendFileSync(process.env.ORCA_NATIVE_TEST_LOG, \`pnpm \${process.argv.slice(2).join(' ')}\\n\`) +const { appendFileSync, writeFileSync, writeSync } = require('node:fs') +appendFileSync(process.env.ORCA_NATIVE_TEST_LOG, \`node-gyp \${process.argv.slice(2).join(' ')}\\n\`) appendFileSync(process.env.ORCA_NATIVE_TEST_LOG, \`cwd=\${process.cwd()}\\n\`) -appendFileSync( - process.env.ORCA_NATIVE_TEST_LOG, - \`npm_config_build_from_source=\${process.env.npm_config_build_from_source || ''}\\n\` -) -appendFileSync( - process.env.ORCA_NATIVE_TEST_LOG, - \`cxxflags=\${process.env.CXXFLAGS || ''}\\n\` -) +appendFileSync(process.env.ORCA_NATIVE_TEST_LOG, \`cxxflags=\${process.env.CXXFLAGS || ''}\\n\`) +if (process.env.ORCA_NATIVE_TEST_VERBOSE_OUTPUT_BYTES) { + const output = Buffer.alloc(Number(process.env.ORCA_NATIVE_TEST_VERBOSE_OUTPUT_BYTES), 'x') + for (let offset = 0; offset < output.length;) { + offset += writeSync(1, output.subarray(offset)) + } + writeSync(1, 'node-gyp stdout complete\\n') + writeSync(2, 'node-gyp stderr complete\\n') +} writeFileSync(process.env.ORCA_NATIVE_TEST_MARKER, 'rebuilt') ` ) - - const posixPnpmPath = join(binDir, 'pnpm') - writeFileSync(posixPnpmPath, `#!/usr/bin/env node\nrequire(${JSON.stringify(shimPath)})\n`) - chmodSync(posixPnpmPath, 0o755) - writeFileSync( - join(binDir, 'pnpm.cmd'), - `@echo off\r\n"${process.execPath}" "%~dp0\\pnpm-shim.cjs" %*\r\n` - ) } diff --git a/config/scripts/linear-sdk-runtime.test.mjs b/config/scripts/linear-sdk-runtime.test.mjs new file mode 100644 index 00000000000..896b8c7b7d5 --- /dev/null +++ b/config/scripts/linear-sdk-runtime.test.mjs @@ -0,0 +1,78 @@ +import { mkdtemp, rm } from 'node:fs/promises' +import { createRequire } from 'node:module' +import { join, resolve } from 'node:path' +import { build } from 'vite' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { electronViteConfig } from '../../electron.vite.config' +import { runProcess } from '../../src/shared/child-process/run-process' + +const projectDir = resolve(import.meta.dirname, '../..') +const require = createRequire(import.meta.url) +let outputDir + +const smokeScript = ` + const assert = require('node:assert/strict') + const { createRequire } = require('node:module') + const loaderPath = process.argv[1] + const requireFromLoader = createRequire(loaderPath) + const sdkEntry = requireFromLoader.resolve('@linear/sdk') + const { loadLinearSdk } = require(loaderPath) + assert.equal(require.cache[sdkEntry], undefined, 'SDK must remain lazy') + + const sdk = loadLinearSdk() + assert.equal(sdk, requireFromLoader('@linear/sdk')) + assert.equal(loadLinearSdk(), sdk, 'repeat loads must reuse the SDK') + const client = new sdk.LinearClient({ apiKey: 'orca-offline-smoke-test' }) + assert.equal(typeof client.issues, 'function') + assert.equal(typeof client.teams, 'function') + assert.ok(sdk.AuthenticationLinearError.prototype instanceof Error) + console.log('Linear SDK loaded') +` + +beforeAll(async () => { + // Keep normal dependency resolution while executing outside Vitest's module loader. + outputDir = await mkdtemp(join(projectDir, 'node_modules', 'orca-linear-sdk-runtime-')) + const mainBuild = electronViteConfig.main.build + await build({ + configFile: false, + publicDir: false, + logLevel: 'silent', + build: { + ssr: true, + minify: mainBuild.minify, + outDir: outputDir, + rollupOptions: { + external: mainBuild.rollupOptions.external, + input: join(projectDir, 'src/main/linear/linear-sdk.ts'), + output: { format: 'cjs', entryFileNames: 'linear-sdk.cjs' } + } + } + }) +}) + +afterAll(async () => { + if (outputDir) { + await rm(outputDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) + } +}) + +describe('resolved Linear SDK through the production CommonJS loader', () => { + it.each([ + { name: 'Node', program: process.execPath }, + { name: 'Electron', program: require('electron') } + ])( + 'loads and constructs the real SDK under $name without network access', + async ({ program }) => { + const result = await runProcess({ + program, + args: ['-e', smokeScript, join(outputDir, 'linear-sdk.cjs')], + cwd: projectDir, + env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', ELECTRON_RUN_AS_NODE: '1' }, + timeoutMs: 20000 + }) + expect(result.code, result.stderr).toBe(0) + expect(result.timedOut).toBe(false) + expect(result.stdout.trim()).toBe('Linear SDK loaded') + } + ) +}) diff --git a/config/scripts/patched-dependencies-frozen-install.test.mjs b/config/scripts/patched-dependencies-frozen-install.test.mjs index 89f98ef074b..53061c1dbd7 100644 --- a/config/scripts/patched-dependencies-frozen-install.test.mjs +++ b/config/scripts/patched-dependencies-frozen-install.test.mjs @@ -61,6 +61,11 @@ describe('patched dependencies', () => { for (const file of ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml']) { copyFileSync(join(PROJECT_DIR, file), join(scratch, file)) } + mkdirSync(join(scratch, 'native', 'windows-registry'), { recursive: true }) + copyFileSync( + join(PROJECT_DIR, 'native', 'windows-registry', 'package.json'), + join(scratch, 'native', 'windows-registry', 'package.json') + ) mkdirSync(join(scratch, 'config'), { recursive: true }) cpSync(join(PROJECT_DIR, 'config', 'patches'), join(scratch, 'config', 'patches'), { recursive: true diff --git a/config/scripts/pr-workflow-parallelism.test.mjs b/config/scripts/pr-workflow-parallelism.test.mjs index 9f841ffb1be..611dd974b79 100644 --- a/config/scripts/pr-workflow-parallelism.test.mjs +++ b/config/scripts/pr-workflow-parallelism.test.mjs @@ -285,7 +285,7 @@ describe('PR workflow parallelism', () => { expect(pnpmIndex).toBeLessThan(nodeIndex) expect(pnpmIndex).toBeLessThan(requestedNodeIndex) const packageManagerVersion = /^pnpm@([^+]+)/.exec(packageJson.packageManager)?.[1] - expect(packageManagerVersion).toBe('12.0.0') + expect(packageManagerVersion).toBe('12.8.1') expect(steps[pnpmIndex].uses).toBe('pnpm/setup@v2') expect(steps[pnpmIndex].with.version).toBeUndefined() expect(steps[pnpmIndex].with.install).toBe(false) diff --git a/config/scripts/windows-cmd-shim-spawn-boundary.test.mjs b/config/scripts/windows-cmd-shim-spawn-boundary.test.mjs index 80263ec4b65..a4a267b45ce 100644 --- a/config/scripts/windows-cmd-shim-spawn-boundary.test.mjs +++ b/config/scripts/windows-cmd-shim-spawn-boundary.test.mjs @@ -29,7 +29,6 @@ const WINDOWS_SHIM_SPAWN_ALLOWLIST = [ 'config/scripts/dev-cli-terminal-wrapper.mjs', 'config/scripts/dev-cli-terminal-wrapper.test.mjs', 'config/scripts/electron-builder-config.test.mjs', - 'config/scripts/ensure-native-runtime.test.mjs', 'config/scripts/live-remote-freeze-rpc.mjs', 'config/scripts/pty-transcript-secret-scan.test.mjs', 'config/scripts/remote-agent-session-authority-repro.mjs', diff --git a/config/scripts/windows-process-tree-gyp-rebuild.mjs b/config/scripts/windows-process-tree-gyp-rebuild.mjs index ba3c699a7a6..c64df18fa89 100644 --- a/config/scripts/windows-process-tree-gyp-rebuild.mjs +++ b/config/scripts/windows-process-tree-gyp-rebuild.mjs @@ -54,13 +54,13 @@ export const WINDOWS_PROCESS_TREE_NODE_ADDON_API_HEADERS = [ 'napi-inl.deprecated.h' ] -export function nodeGypRebuildInvocation(arch, packageDir = WINDOWS_PROCESS_TREE_PACKAGE_DIR) { +export function nodeGypRebuildInvocation( + arch, + packageDir = WINDOWS_PROCESS_TREE_PACKAGE_DIR, + nodeGypEntry = join(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js') +) { return { - args: [ - join(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js'), - 'rebuild', - `--arch=${arch}` - ], + args: [nodeGypEntry, 'rebuild', `--arch=${arch}`], cwd: realpathSync(packageDir) } } diff --git a/config/scripts/windows-process-tree-gyp-rebuild.test.mjs b/config/scripts/windows-process-tree-gyp-rebuild.test.mjs index a33506116f1..73756af1659 100644 --- a/config/scripts/windows-process-tree-gyp-rebuild.test.mjs +++ b/config/scripts/windows-process-tree-gyp-rebuild.test.mjs @@ -46,6 +46,14 @@ describe('windows-process-tree node-gyp rebuild', () => { expect(args).toContain('--arch=arm64') }) + it('preserves an external node-gyp entry and the physical addon cwd', () => { + const entry = join(tmpdir(), 'external-node-gyp', 'bin', 'node-gyp.js') + expect(nodeGypRebuildInvocation('arm64', import.meta.dirname, entry)).toEqual({ + args: [entry, 'rebuild', '--arch=arm64'], + cwd: realpathSync(import.meta.dirname) + }) + }) + it('copies node-addon-api headers into the patched include dir', () => { const packageDir = mkdtempSync(join(tmpdir(), 'orca-windows-process-tree-headers-')) try { diff --git a/docs/site/.gitignore b/docs/site/.gitignore index 104c6398aca..61917c0abcb 100644 --- a/docs/site/.gitignore +++ b/docs/site/.gitignore @@ -8,4 +8,4 @@ next-env.d.ts # Keep local deployment credentials out of the publication boundary. .env .env.* -.npmrc +# Project .npmrc contains package policy; keep credentials in user-level npm config. diff --git a/docs/site/.npmrc b/docs/site/.npmrc new file mode 100644 index 00000000000..d7913f44df7 --- /dev/null +++ b/docs/site/.npmrc @@ -0,0 +1 @@ +minimum-release-age=4320 diff --git a/docs/site/package.json b/docs/site/package.json index 89a977c3d40..010405f418e 100644 --- a/docs/site/package.json +++ b/docs/site/package.json @@ -18,21 +18,21 @@ "fumadocs-mdx": "^14.3.1", "fumadocs-ui": "^16.8.4", "lucide-react": "^1.6.0", - "next": "16.3.4", - "react": "19.2.4", - "react-dom": "19.2.4", - "tailwind-merge": "^3.5.0", + "next": "16.3.6", + "react": "19.2.8", + "react-dom": "19.2.8", + "tailwind-merge": "^3.7.0", "tw-animate-css": "^1.4.0", "zod": "^4" }, "devDependencies": { "@tailwindcss/postcss": "^4", "@types/mdx": "^2.0.13", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/node": "^22.20.4", + "@types/react": "~19.2.18", + "@types/react-dom": "~19.2.7", "eslint": "^9", - "eslint-config-next": "16.3.4", + "eslint-config-next": "16.3.6", "tailwindcss": "^4", "typescript": "^5", "vercel": "59.11.1" @@ -40,7 +40,7 @@ "engines": { "node": "22.x" }, - "packageManager": "pnpm@10.24.0", + "packageManager": "pnpm@10.34.6", "pnpm": { "onlyBuiltDependencies": [ "esbuild", @@ -50,13 +50,21 @@ "overrides": { "@vercel/fun>tar": "7.5.22", "@vercel/fun>@tootallnate/once": "2.0.1", - "@vercel/node>undici": "6.28.0", + "@vercel/node>undici": "6.28.1", "@vercel/python-analysis>js-yaml": "4.3.2", "@vercel/python-analysis>minimatch": "10.2.6", "vercel>smol-toml": "1.8.0", - "vercel>undici": "6.28.0", + "vercel>undici": "6.28.1", "@vercel/python-analysis>smol-toml": "1.8.0", - "@vercel/rust>smol-toml": "1.8.0" + "@vercel/rust>smol-toml": "1.8.0", + "@vercel/sandbox>undici": "7.29.1", + "@vercel/static-config>ajv": "8.18.0", + "@vercel/backends>path-to-regexp": "8.4.0", + "@vercel/fun>path-to-regexp": "8.4.0", + "brace-expansion@<2": "1.1.21", + "brace-expansion@>=4 <5.0.12": "5.0.12", + "@vercel/express>path-to-regexp": "8.4.0", + "@vercel/hono>path-to-regexp": "8.4.0" } } } diff --git a/docs/site/pnpm-lock.yaml b/docs/site/pnpm-lock.yaml index 0d496ad7c6c..c69df84feae 100644 --- a/docs/site/pnpm-lock.yaml +++ b/docs/site/pnpm-lock.yaml @@ -7,13 +7,21 @@ settings: overrides: '@vercel/fun>tar': 7.5.22 '@vercel/fun>@tootallnate/once': 2.0.1 - '@vercel/node>undici': 6.28.0 + '@vercel/node>undici': 6.28.1 '@vercel/python-analysis>js-yaml': 4.3.2 '@vercel/python-analysis>minimatch': 10.2.6 vercel>smol-toml: 1.8.0 - vercel>undici: 6.28.0 + vercel>undici: 6.28.1 '@vercel/python-analysis>smol-toml': 1.8.0 '@vercel/rust>smol-toml': 1.8.0 + '@vercel/sandbox>undici': 7.29.1 + '@vercel/static-config>ajv': 8.18.0 + '@vercel/backends>path-to-regexp': 8.4.0 + '@vercel/fun>path-to-regexp': 8.4.0 + brace-expansion@<2: 1.1.21 + brace-expansion@>=4 <5.0.12: 5.0.12 + '@vercel/express>path-to-regexp': 8.4.0 + '@vercel/hono>path-to-regexp': 8.4.0 importers: @@ -24,28 +32,28 @@ importers: version: 2.1.1 fumadocs-core: specifier: ^16.8.4 - version: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3) + version: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) fumadocs-mdx: specifier: ^14.3.1 - version: 14.3.2(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 14.3.2(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.18)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) fumadocs-ui: specifier: ^16.8.4 - version: 16.12.1(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.3.3) + version: 16.12.1(@types/mdx@2.0.14)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3) lucide-react: specifier: ^1.6.0 - version: 1.26.0(react@19.2.4) + version: 1.26.0(react@19.2.8) next: - specifier: 16.3.4 - version: 16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 16.3.6 + version: 16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: - specifier: 19.2.4 - version: 19.2.4 + specifier: 19.2.8 + version: 19.2.8 react-dom: - specifier: 19.2.4 - version: 19.2.4(react@19.2.4) + specifier: 19.2.8 + version: 19.2.8(react@19.2.8) tailwind-merge: - specifier: ^3.5.0 - version: 3.6.0 + specifier: ^3.7.0 + version: 3.7.0 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -60,20 +68,20 @@ importers: specifier: ^2.0.13 version: 2.0.14 '@types/node': - specifier: ^20 - version: 20.19.43 + specifier: ^22.20.4 + version: 22.20.4 '@types/react': - specifier: ^19 - version: 19.2.17 + specifier: ~19.2.18 + version: 19.2.18 '@types/react-dom': - specifier: ^19 - version: 19.2.3(@types/react@19.2.17) + specifier: ~19.2.7 + version: 19.2.7(@types/react@19.2.18) eslint: specifier: ^9 version: 9.39.5(jiti@2.7.0) eslint-config-next: - specifier: 16.3.4 - version: 16.3.4(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + specifier: 16.3.6 + version: 16.3.6(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.3.3 @@ -102,8 +110,8 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.29.7': @@ -140,8 +148,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} engines: {node: '>=6.0.0'} hasBin: true @@ -149,12 +157,12 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@bytecodealliance/preview2-shim@0.17.6': @@ -605,144 +613,160 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.35.4': - resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.35.4': - resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.4': - resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} engines: {node: '>=20.9.0'} os: [freebsd] - '@img/sharp-libvips-darwin-arm64@1.3.3': - resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.3': - resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.3.3': - resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} cpu: [arm64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-arm@1.3.3': - resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} cpu: [arm] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.3.3': - resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.3.3': - resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.3.3': - resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} cpu: [s390x] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linux-x64@1.3.3': - resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} cpu: [x64] os: [linux] + libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': - resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} cpu: [arm64] os: [linux] + libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.3.3': - resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} cpu: [x64] os: [linux] + libc: [musl] - '@img/sharp-linux-arm64@0.35.4': - resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [glibc] - '@img/sharp-linux-arm@0.35.4': - resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] + libc: [glibc] - '@img/sharp-linux-ppc64@0.35.4': - resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] + libc: [glibc] - '@img/sharp-linux-riscv64@0.35.4': - resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] + libc: [glibc] - '@img/sharp-linux-s390x@0.35.4': - resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] + libc: [glibc] - '@img/sharp-linux-x64@0.35.4': - resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.35.4': - resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [musl] - '@img/sharp-linuxmusl-x64@0.35.4': - resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [musl] - '@img/sharp-wasm32@0.35.4': - resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} engines: {node: '>=20.9.0'} - '@img/sharp-webcontainers-wasm32@0.35.4': - resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.35.4': - resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.35.4': - resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.35.4': - resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -804,30 +828,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/keyring-linux-arm64-musl@1.2.0': resolution: {integrity: sha512-8TDymrpC4P1a9iDEaegT7RnrkmrJN5eNZh3Im3UEV5PPYGtrb82CRxsuFohthCWQW81O483u1bu+25+XA4nKUw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/keyring-linux-riscv64-gnu@1.2.0': resolution: {integrity: sha512-awsB5XI1MYL7fwfjMDGmKOWvNgJEO7mM7iVEMS0fO39f0kVJnOSjlu7RHcXAF0LOx+0VfF3oxbWqJmZbvRCRHw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/keyring-linux-x64-gnu@1.2.0': resolution: {integrity: sha512-8E+7z4tbxSJXxIBqA+vfB1CGajpCDRyTyqXkBig5NtASrv4YXcntSo96Iah2QDR5zD3dSTsmbqJudcj9rKKuHQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/keyring-linux-x64-musl@1.2.0': resolution: {integrity: sha512-8RZ8yVEnmWr/3BxKgBSzmgntI7lNEsY7xouNfOsQkuVAiCNmxzJwETspzK3PQ2FHtDxgz5vHQDEBVGMyM4hUHA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/keyring-win32-arm64-msvc@1.2.0': resolution: {integrity: sha512-AoqaDZpQ6KPE19VBLpxyORcp+yWmHI9Xs9Oo0PJ4mfHma4nFSLVdhAubJCxdlNptHe5va7ghGCHj3L9Akiv4cQ==} @@ -857,56 +886,67 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.3.4': - resolution: {integrity: sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==} + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - '@next/eslint-plugin-next@16.3.4': - resolution: {integrity: sha512-szW9y2Aumu4z88YXfTzcFsgUAg2k64uzbtcO5L9f1AKS4w/GUKJcbFllRflROVyNPgJtGOnvNxiyp3v6b+prIA==} + '@next/env@16.3.6': + resolution: {integrity: sha512-x9Vblze1EbtltQYnNH38xCPWU3TVfBd1eXqA3+w9+BTpedkkdNpAaltXlGQ/nsc1+E0mVTNrtcbX3GoO09zeLQ==} - '@next/swc-darwin-arm64@16.3.4': - resolution: {integrity: sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw==} + '@next/eslint-plugin-next@16.3.6': + resolution: {integrity: sha512-jowwDX+7DOlDIjJLgTMxudw+k37QnWu1JkZLkSi9MaJBfDYcfhAPMKBhXL0idYzFN/AGg//axnOR4cLkHX/Rng==} + + '@next/swc-darwin-arm64@16.3.6': + resolution: {integrity: sha512-E/7GEqaUkt8mk/T8v9lAnrhzR06kdq1ZBkC12F8tAMkdIadwNp3H1KqHynDHrpcTlGCUdq/qu6vUL2aYVyYBdw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.3.4': - resolution: {integrity: sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg==} + '@next/swc-darwin-x64@16.3.6': + resolution: {integrity: sha512-yBE893/nDWTlaiBD1p+qgt7NUen4U5R6FXyH0s67Npq1S3E0cVSef1WIXC2xBRgQvwAvJq6DnS6Y6PrY0cy4Ew==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.3.4': - resolution: {integrity: sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA==} + '@next/swc-linux-arm64-gnu@16.3.6': + resolution: {integrity: sha512-KJDpjBqBPYlvkivmyrp+Qys6k/7ksbqGQvRVc6ZEGfR+cjQxx+nUkJaWmNZJsmoOrqYNbaXByF8wa0lBwDhB3Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] - '@next/swc-linux-arm64-musl@16.3.4': - resolution: {integrity: sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg==} + '@next/swc-linux-arm64-musl@16.3.6': + resolution: {integrity: sha512-mqNg2K+hvWskSRb/QM+Ix412DvBsuSF0XV+frTSw5vmoucNnIlynFwKYew8D01bfATErMOM7Bujrf0BA5DRKFA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] - '@next/swc-linux-x64-gnu@16.3.4': - resolution: {integrity: sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw==} + '@next/swc-linux-x64-gnu@16.3.6': + resolution: {integrity: sha512-nFncBNGAYouRHjRVaITs9beZRfhX4ssVwpnvPIAbkZVH6LtGoAVlH4bJ8Cnf9SOo9bsXgPFer/GdHtEE3JNOkw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] - '@next/swc-linux-x64-musl@16.3.4': - resolution: {integrity: sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q==} + '@next/swc-linux-x64-musl@16.3.6': + resolution: {integrity: sha512-5Mf3cHDGR/Iz0ng2Bj3zUR3p5QS9YK3Hn2QiAfavFmyF48zwThAjpFoiTKNIcOHLYS4zEk+gzyJ/9deQ2ZB8yQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] - '@next/swc-win32-arm64-msvc@16.3.4': - resolution: {integrity: sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A==} + '@next/swc-win32-arm64-msvc@16.3.6': + resolution: {integrity: sha512-0jkJy0C2kbrJWTk4YLa3xk80pVBpx8FCHJym7CnUfDAXe/FWv5qT7SQJbR0KuemyxaEDlEx5WT4VQJoTW+/9Qw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.3.4': - resolution: {integrity: sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw==} + '@next/swc-win32-x64-msvc@16.3.6': + resolution: {integrity: sha512-/YXjI1e5OXcZ7YpxRwgP/1jAV/SBKTzeVKqN2mk7mLpcICsyn3Gl5+dIfDTJp70M0ccMhyMMRso4v6mPDCGepg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -978,48 +1018,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-arm64-musl@0.121.0': resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-musl@0.121.0': resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-s390x-gnu@0.121.0': resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.121.0': resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-musl@0.121.0': resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-parser/binding-openharmony-arm64@0.121.0': resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==} @@ -1103,48 +1151,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-arm64-musl@0.111.0': resolution: {integrity: sha512-ARyfcMCIxVLDgLf6FQ8Oo1/TFySpnquV+vuSb4SFQZfYDqgMklzwv0NYXxWD0aB6enElyMDs6pQJBzusEKCkOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-ppc64-gnu@0.111.0': resolution: {integrity: sha512-PKpVRrSvBNK3tv9vwxn7Fay+QWZmprPGlEqJcseBJllQc5mFMD4Q/w44chu5iR9ZLsDeSHzmNWrgMLo4J0sP2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-gnu@0.111.0': resolution: {integrity: sha512-9bUml6rMgk+8GF5rvNMweFspkzSiCjqpV6HduwiUyexqfGKrmjq9IZOxxvnzkE2RGdQzP507NNDoVNYIoGQYuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-riscv64-musl@0.111.0': resolution: {integrity: sha512-tzGCohGxaeH6KRJjfYZd4mHCoGjCai6N+zZi1Oj+tSDMAAdyvs1dRzYb8PNUGnybCg3Te4M0jLPzWZaSmnKraQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-transform/binding-linux-s390x-gnu@0.111.0': resolution: {integrity: sha512-sRG1KIfZ0ML9ToEygm5aM/5GJeBA05uHlgW3M0Rx/DNWMJhuahLmqWuB02aWSmijndLfEKXLLXIWhvWupRG8lg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-gnu@0.111.0': resolution: {integrity: sha512-T0Kmvk+OdlUdABdXlDIf3MQReMzFfC75NEI9x8jxy5pKooACEFg0k0V8gyR3gq4DzbDCfucqFQDWNvSgIopAbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-transform/binding-linux-x64-musl@0.111.0': resolution: {integrity: sha512-EgoutsP3YfqzN8a9vpc9+XLr0bmBl0dA3uOMiP77+exATCPxJBkJErGmQkqk6RtTp5XqX6q6mB45qWQyKk6+pA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-transform/binding-openharmony-arm64@0.111.0': resolution: {integrity: sha512-d8J+ejc0j5WODbVwR/QxFaI65YMwvG0W53vcVCHwa6ja1QI5lpe7sislrefG2EFYgnY47voMRzlXab5d4gEcDw==} @@ -1570,24 +1626,28 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} @@ -1705,24 +1765,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.3': resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.3': resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.3': resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.3': resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} @@ -1765,6 +1829,9 @@ packages: '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1795,16 +1862,16 @@ packages: '@types/node@20.11.0': resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==} - '@types/node@20.19.43': - resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + '@types/node@22.20.4': + resolution: {integrity: sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.2.7': + resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1812,63 +1879,63 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@typescript-eslint/eslint-plugin@8.65.0': - resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.65.0 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.65.0': - resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} 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.65.0': - resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.65.0': - resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.65.0': - resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.65.0': - resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} 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/types@8.65.0': - resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.65.0': - resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.65.0': - resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} 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/visitor-keys@8.65.0': - resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.3': @@ -1913,51 +1980,61 @@ packages: resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.12.2': resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} cpu: [loong64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-loong64-musl@1.12.2': resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} cpu: [loong64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.12.2': resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.12.2': resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-openharmony-arm64@1.12.2': resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} @@ -2191,8 +2268,8 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.6.3: - resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -2219,8 +2296,8 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + array-includes@3.2.0: + resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==} engines: {node: '>= 0.4'} array.prototype.findlast@1.2.5: @@ -2279,8 +2356,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -2313,27 +2390,27 @@ packages: bare-abort-controller: optional: true - baseline-browser-mapping@2.11.1: - resolution: {integrity: sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==} + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} engines: {node: '>=6.0.0'} hasBin: true bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - brace-expansion@1.1.18: - resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + browserslist@4.29.2: + resolution: {integrity: sha512-/u9r8k8ue4ZhHCw9ovDtltpWdXeXzjhdxCGj6vm1RGLPXb8lu33EMdoCHF5Sx8oYBS+Q/FYYNmAqlHncxR3RmA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2360,8 +2437,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001813: + resolution: {integrity: sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2545,8 +2622,8 @@ packages: engines: {node: '>=16'} hasBin: true - electron-to-chromium@1.5.396: - resolution: {integrity: sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==} + electron-to-chromium@1.5.440: + resolution: {integrity: sha512-SghDzqdJokdz8zP8YNlvS74+CwLRoUPDGvP/gFA+HrKVw+pOshUAgx8pXR37xl/u16zGae746rFNWvZ/22kU2Q==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2635,8 +2712,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@16.3.4: - resolution: {integrity: sha512-35/8RM10huEL9vlr8hUZMERMENHBrnyHN3ZZkF9efSgzGaqK34jIqry44A956//zriUhUAUW0XSkcolhrryqAA==} + eslint-config-next@16.3.6: + resolution: {integrity: sha512-1Upt3U7BDwU+ilpe2byZjAfts9oNq4d4fv/zXEvs8/4yS+cwOQW/WCxUNy8gCDquX67SzeehDvKblVC6ZBMocQ==} peerDependencies: eslint: '>=9.0.0' typescript: '>=3.3.1' @@ -2823,6 +2900,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -3051,6 +3131,9 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -3172,8 +3255,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.10: + resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -3227,8 +3310,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + is-core-module@2.17.0: + resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -3455,24 +3538,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -3783,6 +3870,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3797,8 +3889,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.3.4: - resolution: {integrity: sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA==} + next@16.3.6: + resolution: {integrity: sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -3853,8 +3945,8 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} engines: {node: '>=18'} nopt@8.1.0: @@ -3992,12 +4084,8 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} - - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.0: + resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -4059,10 +4147,10 @@ packages: resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} engines: {node: '>= 0.8'} - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: - react: ^19.2.4 + react: ^19.2.8 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4097,8 +4185,8 @@ packages: '@types/react': optional: true - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} engines: {node: '>=0.10.0'} readdirp@4.1.2: @@ -4258,8 +4346,8 @@ packages: setprototypeof@1.1.1: resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} - sharp@0.35.4: - resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} engines: {node: '>=20.9.0'} peerDependencies: '@types/node': '*' @@ -4350,8 +4438,8 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: @@ -4411,8 +4499,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -4509,16 +4597,16 @@ packages: resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + typed-array-byte-offset@1.0.5: + resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==} engines: {node: '>= 0.4'} typed-array-length@1.0.8: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.65.0: - resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4542,12 +4630,12 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@6.28.0: - resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} + undici@6.28.1: + resolution: {integrity: sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==} engines: {node: '>=18.17'} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + undici@7.29.1: + resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==} engines: {node: '>=20.18.1'} unified@11.0.5: @@ -4585,8 +4673,8 @@ packages: unrs-resolver@1.12.2: resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -4656,8 +4744,8 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.22: - resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + which-typed-array@1.1.24: + resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==} engines: {node: '>= 0.4'} which@2.0.2: @@ -4755,14 +4843,14 @@ snapshots: '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.9 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -4772,10 +4860,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -4784,7 +4872,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.7 + browserslist: 4.29.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -4792,8 +4880,8 @@ snapshots: '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -4802,7 +4890,7 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color @@ -4815,31 +4903,31 @@ snapshots: '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.7': + '@babel/parser@7.29.9': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.9 '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 @@ -5095,20 +5183,20 @@ snapshots: '@floating-ui/core': 1.8.0 '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@floating-ui/dom': 1.8.0 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) '@floating-ui/utils@0.2.12': {} - '@fuma-translate/react@1.0.2(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@fuma-translate/react@1.0.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 '@fumadocs/tailwind@0.1.1(tailwindcss@4.3.3)': optionalDependencies: @@ -5133,108 +5221,108 @@ snapshots: '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.35.4': + '@img/sharp-darwin-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-arm64': 1.3.4 optional: true - '@img/sharp-darwin-x64@0.35.4': + '@img/sharp-darwin-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.4 optional: true - '@img/sharp-freebsd-wasm32@0.35.4': + '@img/sharp-freebsd-wasm32@0.35.5': dependencies: - '@img/sharp-wasm32': 0.35.4 + '@img/sharp-wasm32': 0.35.5 optional: true - '@img/sharp-libvips-darwin-arm64@1.3.3': + '@img/sharp-libvips-darwin-arm64@1.3.4': optional: true - '@img/sharp-libvips-darwin-x64@1.3.3': + '@img/sharp-libvips-darwin-x64@1.3.4': optional: true - '@img/sharp-libvips-linux-arm64@1.3.3': + '@img/sharp-libvips-linux-arm64@1.3.4': optional: true - '@img/sharp-libvips-linux-arm@1.3.3': + '@img/sharp-libvips-linux-arm@1.3.4': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.3': + '@img/sharp-libvips-linux-ppc64@1.3.4': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.3': + '@img/sharp-libvips-linux-riscv64@1.3.4': optional: true - '@img/sharp-libvips-linux-s390x@1.3.3': + '@img/sharp-libvips-linux-s390x@1.3.4': optional: true - '@img/sharp-libvips-linux-x64@1.3.3': + '@img/sharp-libvips-linux-x64@1.3.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.3.3': + '@img/sharp-libvips-linuxmusl-x64@1.3.4': optional: true - '@img/sharp-linux-arm64@0.35.4': + '@img/sharp-linux-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.4 optional: true - '@img/sharp-linux-arm@0.35.4': + '@img/sharp-linux-arm@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.4 optional: true - '@img/sharp-linux-ppc64@0.35.4': + '@img/sharp-linux-ppc64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.4 optional: true - '@img/sharp-linux-riscv64@0.35.4': + '@img/sharp-linux-riscv64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.4 optional: true - '@img/sharp-linux-s390x@0.35.4': + '@img/sharp-linux-s390x@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.4 optional: true - '@img/sharp-linux-x64@0.35.4': + '@img/sharp-linux-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.4 optional: true - '@img/sharp-linuxmusl-arm64@0.35.4': + '@img/sharp-linuxmusl-arm64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 optional: true - '@img/sharp-linuxmusl-x64@0.35.4': + '@img/sharp-linuxmusl-x64@0.35.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 optional: true - '@img/sharp-wasm32@0.35.4': + '@img/sharp-wasm32@0.35.5': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.4': + '@img/sharp-webcontainers-wasm32@0.35.5': dependencies: - '@img/sharp-wasm32': 0.35.4 + '@img/sharp-wasm32': 0.35.5 optional: true - '@img/sharp-win32-arm64@0.35.4': + '@img/sharp-win32-arm64@0.35.5': optional: true - '@img/sharp-win32-ia32@0.35.4': + '@img/sharp-win32-ia32@0.35.5': optional: true - '@img/sharp-win32-x64@0.35.4': + '@img/sharp-win32-x64@0.35.5': optional: true '@isaacs/fs-minipass@4.0.1': @@ -5354,13 +5442,6 @@ snapshots: '@napi-rs/keyring-win32-ia32-msvc': 1.2.0 '@napi-rs/keyring-win32-x64-msvc': 1.2.0 - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.3 - optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3)': dependencies: '@emnapi/core': 1.10.0 @@ -5368,37 +5449,44 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@16.3.4': {} + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.4 + optional: true - '@next/eslint-plugin-next@16.3.4(eslint@9.39.5(jiti@2.7.0))': + '@next/env@16.3.6': {} + + '@next/eslint-plugin-next@16.3.6(eslint@9.39.5(jiti@2.7.0))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) fast-glob: 3.3.1 transitivePeerDependencies: - eslint - '@next/swc-darwin-arm64@16.3.4': + '@next/swc-darwin-arm64@16.3.6': optional: true - '@next/swc-darwin-x64@16.3.4': + '@next/swc-darwin-x64@16.3.6': optional: true - '@next/swc-linux-arm64-gnu@16.3.4': + '@next/swc-linux-arm64-gnu@16.3.6': optional: true - '@next/swc-linux-arm64-musl@16.3.4': + '@next/swc-linux-arm64-musl@16.3.6': optional: true - '@next/swc-linux-x64-gnu@16.3.4': + '@next/swc-linux-x64-gnu@16.3.6': optional: true - '@next/swc-linux-x64-musl@16.3.4': + '@next/swc-linux-x64-musl@16.3.6': optional: true - '@next/swc-win32-arm64-msvc@16.3.4': + '@next/swc-win32-arm64-msvc@16.3.6': optional: true - '@next/swc-win32-x64-msvc@16.3.4': + '@next/swc-win32-x64-msvc@16.3.6': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5555,352 +5643,352 @@ snapshots: '@radix-ui/primitive@1.1.7': {} - '@radix-ui/react-accordion@1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-accordion@1.2.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collapsible': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-arrow@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-arrow@1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-collapsible@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collapsible@1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-collection@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.1(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-compose-refs@1.1.4(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-context@1.2.1(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-context@1.2.1(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-dialog@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dialog@1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-portal': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.14(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-portal': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-direction@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-direction@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-dismissable-layer@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dismissable-layer@1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-effect-event': 0.0.4(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-focus-guards@1.1.5(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-focus-guards@1.1.5(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-focus-scope@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-focus-scope@1.1.14(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-id@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-id@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-navigation-menu@1.2.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-navigation-menu@1.2.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-previous': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-popover@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popover@1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-popper': 1.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.14(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.5(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-popper@1.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popper@1.3.5(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.3(@types/react@19.2.17)(react@19.2.4) + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-arrow': 1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-rect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.3(@types/react@19.2.18)(react@19.2.8) '@radix-ui/rect': 1.1.3 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-portal@1.1.15(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-presence@1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-slot': 1.3.1(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-roving-focus@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-roving-focus@1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-is-hydrated': 0.1.2(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-collection': 1.1.13(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-scroll-area@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-scroll-area@1.2.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-slot@1.3.1(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-slot@1.3.1(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-tabs@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tabs@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-id': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-context': 1.2.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.5(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-use-callback-ref@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-callback-ref@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-controllable-state@1.2.5(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-controllable-state@1.2.5(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-use-effect-event': 0.0.4(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-effect-event': 0.0.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-effect-event@0.0.4(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.4(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-is-hydrated@0.1.2(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-is-hydrated@0.1.2(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-previous@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-previous@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-rect@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-rect@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/rect': 1.1.3 - react: 19.2.4 + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-size@1.1.3(@types/react@19.2.17)(react@19.2.4)': + '@radix-ui/react-use-size@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.18)(react@19.2.8) + react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-visually-hidden@1.2.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-visually-hidden@1.2.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-primitive': 2.1.8(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) '@radix-ui/rect@1.1.3': {} @@ -6091,6 +6179,11 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.8.1 + optional: true + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -6121,15 +6214,15 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.19.43': + '@types/node@22.20.4': dependencies: undici-types: 6.21.0 - '@types/react-dom@19.2.3(@types/react@19.2.17)': + '@types/react-dom@19.2.7(@types/react@19.2.18)': dependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@types/react@19.2.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 @@ -6137,57 +6230,57 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.0 eslint: 9.39.5(jiti@2.7.0) - ignore: 7.0.6 + ignore: 7.0.10 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3 eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.71.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.65.0': + '@typescript-eslint/scope-manager@8.71.0': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 - '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.5(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6195,14 +6288,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.65.0': {} + '@typescript-eslint/types@8.71.0': {} - '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.71.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/project-service': 8.71.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 @@ -6212,20 +6305,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/utils@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.65.0': + '@typescript-eslint/visitor-keys@8.71.0': dependencies: - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.3.3': {} @@ -6288,7 +6381,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': @@ -6309,7 +6402,7 @@ snapshots: fs-extra: 11.1.0 get-port: 5.1.1 oxc-transform: 0.111.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3) - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.0 resolve.exports: 2.0.3 rolldown: 1.0.0-rc.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3) srvx: 0.11.16 @@ -6330,7 +6423,7 @@ snapshots: is-buffer: 2.0.5 is-node-process: 1.2.0 throttleit: 2.1.0 - undici: 6.28.0 + undici: 6.28.1 '@vercel/build-utils@14.9.0': dependencies: @@ -6393,7 +6486,7 @@ snapshots: '@vercel/node': 12.0.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3)(@vercel/build-utils@14.9.0) '@vercel/static-config': 3.4.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3) fs-extra: 11.1.0 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.0 ts-morph: 12.0.0 zod: 3.22.4 transitivePeerDependencies: @@ -6424,7 +6517,7 @@ snapshots: micro: 9.3.5-canary.3 ms: 2.1.1 node-fetch: 2.6.7 - path-to-regexp: 8.2.0 + path-to-regexp: 8.4.0 promisepipe: 3.0.0 semver: 7.5.4 stat-mode: 0.3.0 @@ -6474,7 +6567,7 @@ snapshots: '@vercel/node': 12.0.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3)(@vercel/build-utils@14.9.0) '@vercel/static-config': 3.4.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3) fs-extra: 11.1.0 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.0 ts-morph: 12.0.0 zod: 3.22.4 transitivePeerDependencies: @@ -6568,7 +6661,7 @@ snapshots: ts-morph: 12.0.0 tsx: 4.21.0 typescript: 5.9.3 - undici: 6.28.0 + undici: 6.28.1 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -6653,7 +6746,7 @@ snapshots: ms: 2.1.3 picocolors: 1.1.1 tar-stream: 3.1.7 - undici: 7.29.0 + undici: 7.29.1 xdg-app-paths: 5.1.0 zod: 4.4.3 transitivePeerDependencies: @@ -6673,7 +6766,7 @@ snapshots: '@vercel/static-config@3.4.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3)': dependencies: - ajv: 8.6.3 + ajv: 8.18.0 json-schema-to-ts: 1.6.4 oxc-parser: 0.121.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.3) ts-morph: 12.0.0 @@ -6716,12 +6809,12 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.6.3: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 ansi-styles@4.3.0: dependencies: @@ -6744,14 +6837,14 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-includes@3.1.9: + array-includes@3.2.0: dependencies: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.24.2 es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 + es-shim-unscopables: 1.1.0 is-string: 1.1.1 math-intrinsics: 1.1.0 @@ -6828,7 +6921,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.12.1: {} + axe-core@4.13.0: {} axobject-query@4.1.0: {} @@ -6842,18 +6935,18 @@ snapshots: bare-events@2.9.2: {} - baseline-browser-mapping@2.11.1: {} + baseline-browser-mapping@2.11.26: {} bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 - brace-expansion@1.1.18: + brace-expansion@1.1.21: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.9: + brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -6861,13 +6954,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.7: + browserslist@4.29.2: dependencies: - baseline-browser-mapping: 2.11.1 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.396 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.7) + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001813 + electron-to-chromium: 1.5.440 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.2) buffer-crc32@0.2.13: {} @@ -6892,7 +6985,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001813: {} ccount@2.0.1: {} @@ -7049,7 +7142,7 @@ snapshots: signal-exit: 4.0.2 time-span: 4.0.0 - electron-to-chromium@1.5.396: {} + electron-to-chromium@1.5.440: {} emoji-regex@9.2.2: {} @@ -7127,10 +7220,10 @@ snapshots: string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 + typed-array-byte-offset: 1.0.5 typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.22 + which-typed-array: 1.1.24 es-define-property@1.0.1: {} @@ -7261,18 +7354,18 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@16.3.4(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): + eslint-config-next@16.3.6(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 16.3.4(eslint@9.39.5(jiti@2.7.0)) + '@next/eslint-plugin-next': 16.3.6(eslint@9.39.5(jiti@2.7.0)) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0)) globals: 16.4.0 - typescript-eslint: 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + typescript-eslint: 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -7284,7 +7377,7 @@ snapshots: eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: 2.16.2 + is-core-module: 2.17.0 resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color @@ -7294,31 +7387,31 @@ snapshots: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 eslint: 9.39.5(jiti@2.7.0) - get-tsconfig: 4.14.0 + get-tsconfig: 4.14.3 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 @@ -7326,9 +7419,9 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) hasown: 2.0.4 - is-core-module: 2.16.2 + is-core-module: 2.17.0 is-glob: 4.0.3 minimatch: 3.1.5 object.fromentries: 2.0.8 @@ -7338,7 +7431,7 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -7347,10 +7440,10 @@ snapshots: eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)): dependencies: aria-query: 5.3.2 - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.12.1 + axe-core: 4.13.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 @@ -7366,7 +7459,7 @@ snapshots: eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)): dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.9 eslint: 9.39.5(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 @@ -7376,7 +7469,7 @@ snapshots: eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)): dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 @@ -7393,7 +7486,7 @@ snapshots: prop-types: 15.8.1 resolve: 2.0.0-next.7 semver: 6.3.1 - string.prototype.matchall: 4.0.12 + string.prototype.matchall: 4.1.0 string.prototype.repeat: 1.0.0 eslint-scope@8.4.0: @@ -7566,6 +7659,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.8: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -7604,14 +7699,14 @@ snapshots: dependencies: is-callable: 1.2.7 - framer-motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + framer-motion@12.42.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: motion-dom: 12.42.2 motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) fs-extra@11.1.0: dependencies: @@ -7628,7 +7723,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3): + fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 @@ -7653,23 +7748,23 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@types/react': 19.2.17 - lucide-react: 1.26.0(react@19.2.4) - next: 16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@types/react': 19.2.18 + lucide-react: 1.26.0(react@19.2.8) + next: 16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) zod: 4.4.3 transitivePeerDependencies: - supports-color - fumadocs-mdx@14.3.2(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + fumadocs-mdx@14.3.2(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.18)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.1 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3) + fumadocs-core: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) js-yaml: 4.3.2 mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 @@ -7685,43 +7780,43 @@ snapshots: optionalDependencies: '@types/mdast': 4.0.4 '@types/mdx': 2.0.14 - '@types/react': 19.2.17 - next: 16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 + '@types/react': 19.2.18 + next: 16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 transitivePeerDependencies: - supports-color - fumadocs-ui@16.12.1(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.3.3): + fumadocs-ui@16.12.1(@types/mdx@2.0.14)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(fumadocs-core@16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3): dependencies: - '@fuma-translate/react': 1.0.2(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@fuma-translate/react': 1.0.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@fumadocs/tailwind': 0.1.1(tailwindcss@4.3.3) - '@radix-ui/react-accordion': 1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collapsible': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-dialog': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-direction': 1.1.3(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-navigation-menu': 1.2.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popover': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-scroll-area': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.4) - '@radix-ui/react-tabs': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-accordion': 1.2.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collapsible': 1.1.18(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-dialog': 1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-direction': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-navigation-menu': 1.2.20(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-popover': 1.1.21(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.9(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-scroll-area': 1.2.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-tabs': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) class-variance-authority: 0.7.1 cnfast: 0.0.8 - fumadocs-core: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.26.0(react@19.2.4))(next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.4.3) - lucide-react: 1.26.0(react@19.2.4) - motion: 12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.4) + fumadocs-core: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.26.0(react@19.2.8))(next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) + lucide-react: 1.26.0(react@19.2.8) + motion: 12.42.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next-themes: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) rehype-raw: 7.0.0 scroll-into-view-if-needed: 3.1.0 shiki: 4.3.1 unist-util-visit: 5.1.0 optionalDependencies: '@types/mdx': 2.0.14 - '@types/react': 19.2.17 - next: 16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/react': 19.2.18 + next: 16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) transitivePeerDependencies: - '@emotion/is-prop-valid' - '@types/react-dom' @@ -7787,6 +7882,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -7979,7 +8078,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.6: {} + ignore@7.0.10: {} import-fresh@3.3.1: dependencies: @@ -8036,7 +8135,7 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.2: + is-core-module@2.17.0: dependencies: hasown: 2.0.4 @@ -8122,7 +8221,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.22 + which-typed-array: 1.1.24 is-weakmap@2.0.2: {} @@ -8197,7 +8296,7 @@ snapshots: jsx-ast-utils@3.3.5: dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 @@ -8290,9 +8389,9 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@1.26.0(react@19.2.4): + lucide-react@1.26.0(react@19.2.8): dependencies: - react: 19.2.4 + react: 19.2.8 luxon@3.7.2: {} @@ -8758,11 +8857,11 @@ snapshots: minimatch@10.2.6: dependencies: - brace-expansion: 5.0.9 + brace-expansion: 5.0.12 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.18 + brace-expansion: 1.1.21 minimist@1.2.8: {} @@ -8780,13 +8879,13 @@ snapshots: motion-utils@12.39.0: {} - motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + motion@12.42.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - framer-motion: 12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + framer-motion: 12.42.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) tslib: 2.8.1 optionalDependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) mri@1.2.0: {} @@ -8798,35 +8897,37 @@ snapshots: nanoid@3.3.18: {} + nanoid@3.3.19: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} - next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next-themes@0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) - next@16.3.4(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.3.6(@babel/core@7.29.7)(@types/node@22.20.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@next/env': 16.3.4 + '@next/env': 16.3.6 '@swc/helpers': 0.5.23 - baseline-browser-mapping: 2.11.1 - caniuse-lite: 1.0.30001806 + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001813 postcss: 8.5.23 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.4) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.8) optionalDependencies: - '@next/swc-darwin-arm64': 16.3.4 - '@next/swc-darwin-x64': 16.3.4 - '@next/swc-linux-arm64-gnu': 16.3.4 - '@next/swc-linux-arm64-musl': 16.3.4 - '@next/swc-linux-x64-gnu': 16.3.4 - '@next/swc-linux-x64-musl': 16.3.4 - '@next/swc-win32-arm64-msvc': 16.3.4 - '@next/swc-win32-x64-msvc': 16.3.4 - sharp: 0.35.4(@types/node@20.19.43) + '@next/swc-darwin-arm64': 16.3.6 + '@next/swc-darwin-x64': 16.3.6 + '@next/swc-linux-arm64-gnu': 16.3.6 + '@next/swc-linux-arm64-musl': 16.3.6 + '@next/swc-linux-x64-gnu': 16.3.6 + '@next/swc-linux-x64-musl': 16.3.6 + '@next/swc-win32-arm64-msvc': 16.3.6 + '@next/swc-win32-x64-msvc': 16.3.6 + sharp: 0.35.5(@types/node@22.20.4) transitivePeerDependencies: - '@babel/core' - '@types/node' @@ -8853,7 +8954,7 @@ snapshots: node-gyp-build@4.8.4: {} - node-releases@2.0.51: {} + node-releases@2.0.57: {} nopt@8.1.0: dependencies: @@ -9052,9 +9153,7 @@ snapshots: path-to-regexp@6.3.0: {} - path-to-regexp@8.2.0: {} - - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.0: {} pend@1.2.0: {} @@ -9070,7 +9169,7 @@ snapshots: postcss@8.5.23: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -9112,41 +9211,41 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-dom@19.2.4(react@19.2.4): + react-dom@19.2.8(react@19.2.8): dependencies: - react: 19.2.4 + react: 19.2.8 scheduler: 0.27.0 react-is@16.13.1: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.4): + react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8): dependencies: - react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.4) + react: 19.2.8 + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.4): + react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.8): dependencies: - react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.4) + react: 19.2.8 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.8) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.4) + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.8) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.4): + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): dependencies: get-nonce: 1.0.1 - react: 19.2.4 + react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - react@19.2.4: {} + react@19.2.8: {} readdirp@4.1.2: {} @@ -9288,7 +9387,7 @@ snapshots: resolve@2.0.0-next.7: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.17.0 node-exports-info: 1.6.2 object-keys: 1.1.1 path-parse: 1.0.7 @@ -9397,38 +9496,38 @@ snapshots: setprototypeof@1.1.1: {} - sharp@0.35.4(@types/node@20.19.43): + sharp@0.35.5(@types/node@22.20.4): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.4 - '@img/sharp-darwin-x64': 0.35.4 - '@img/sharp-freebsd-wasm32': 0.35.4 - '@img/sharp-libvips-darwin-arm64': 1.3.3 - '@img/sharp-libvips-darwin-x64': 1.3.3 - '@img/sharp-libvips-linux-arm': 1.3.3 - '@img/sharp-libvips-linux-arm64': 1.3.3 - '@img/sharp-libvips-linux-ppc64': 1.3.3 - '@img/sharp-libvips-linux-riscv64': 1.3.3 - '@img/sharp-libvips-linux-s390x': 1.3.3 - '@img/sharp-libvips-linux-x64': 1.3.3 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 - '@img/sharp-linux-arm': 0.35.4 - '@img/sharp-linux-arm64': 0.35.4 - '@img/sharp-linux-ppc64': 0.35.4 - '@img/sharp-linux-riscv64': 0.35.4 - '@img/sharp-linux-s390x': 0.35.4 - '@img/sharp-linux-x64': 0.35.4 - '@img/sharp-linuxmusl-arm64': 0.35.4 - '@img/sharp-linuxmusl-x64': 0.35.4 - '@img/sharp-webcontainers-wasm32': 0.35.4 - '@img/sharp-win32-arm64': 0.35.4 - '@img/sharp-win32-ia32': 0.35.4 - '@img/sharp-win32-x64': 0.35.4 - '@types/node': 20.19.43 + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 22.20.4 optional: true shebang-command@2.0.0: @@ -9526,7 +9625,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 - string.prototype.matchall@4.0.12: + string.prototype.matchall@4.1.0: dependencies: call-bind: 1.0.9 call-bound: 1.0.4 @@ -9590,10 +9689,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.4): + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.8): dependencies: client-only: 0.0.1 - react: 19.2.4 + react: 19.2.8 optionalDependencies: '@babel/core': 7.29.7 @@ -9603,7 +9702,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwind-merge@3.6.0: {} + tailwind-merge@3.7.0: {} tailwindcss@4.3.3: {} @@ -9708,13 +9807,12 @@ snapshots: has-proto: 1.2.0 is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.4: + typed-array-byte-offset@1.0.5: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 @@ -9727,12 +9825,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): + typescript-eslint@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: @@ -9753,9 +9851,9 @@ snapshots: undici-types@6.21.0: {} - undici@6.28.0: {} + undici@6.28.1: {} - undici@7.29.0: {} + undici@7.29.1: {} unified@11.0.5: dependencies: @@ -9830,9 +9928,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - update-browserslist-db@1.2.3(browserslist@4.28.7): + update-browserslist-db@1.3.3(browserslist@4.29.2): dependencies: - browserslist: 4.28.7 + browserslist: 4.29.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -9840,20 +9938,20 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.4): + use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8): dependencies: - react: 19.2.4 + react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.4): + use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8): dependencies: detect-node-es: 1.1.0 - react: 19.2.4 + react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 uuid@14.0.1: {} @@ -9893,7 +9991,7 @@ snapshots: luxon: 3.7.2 sandbox: 4.1.0 smol-toml: 1.8.0 - undici: 6.28.0 + undici: 6.28.1 uuid: 14.0.1 zod: 4.1.11 optionalDependencies: @@ -9960,7 +10058,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.22 + which-typed-array: 1.1.24 which-collection@1.0.2: dependencies: @@ -9969,7 +10067,7 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.22: + which-typed-array@1.1.24: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.9 diff --git a/docs/site/src/components/layout/DocsFooter.tsx b/docs/site/src/components/layout/DocsFooter.tsx index e8aed683f5e..084f2e56c66 100644 --- a/docs/site/src/components/layout/DocsFooter.tsx +++ b/docs/site/src/components/layout/DocsFooter.tsx @@ -1,6 +1,8 @@ import Link from 'next/link' import Image from 'next/image' +const currentYear = new Date().getFullYear() + export function DocsFooter() { return (