From d0e427d4c3266395b81a1f37a70236aa4968c99d Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 00:33:47 -0700 Subject: [PATCH 001/179] fix(server): storage cleanup no longer counts pnpm clones as freed space (#22713) Co-authored-by: Claude Opus 5.5 --- apps/server/src/storageCleanup.test.ts | 53 ++++++++++++++++++++++++++ apps/server/src/storageCleanup.ts | 29 ++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/apps/server/src/storageCleanup.test.ts b/apps/server/src/storageCleanup.test.ts index 00fabc7004fb8..48529f28a9d5b 100644 --- a/apps/server/src/storageCleanup.test.ts +++ b/apps/server/src/storageCleanup.test.ts @@ -1241,6 +1241,59 @@ describe("storage cleanup reports and local file policies", () => { }), ), ); + it.live.each(["same-device", "other-device", "no-manifest"] as const)( + "counts the pnpm virtual store only when its store forces copies (%s)", + (store) => + runCleanupTest( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const storeDir = yield* fs.makeTempDirectoryScoped({ prefix: "pnpm-store-" }); + const { service, worktree, setPolicy } = yield* cleanupFixture().pipe( + Effect.provideService(FileSystem.FileSystem, { + ...fs, + stat: (target) => + fs + .stat(target) + .pipe( + Effect.map((info) => + store === "other-device" && target === storeDir + ? { ...info, dev: info.dev + 1n } + : info, + ), + ), + }), + ); + setPolicy("tracked-changes"); + const nodeModules = `${worktree}/node_modules`; + yield* fs.makeDirectory(`${nodeModules}/.pnpm/pkg@1.0.0`, { recursive: true }); + yield* fs.makeDirectory(`${nodeModules}/.vite`); + yield* fs.writeFileString( + `${nodeModules}/.pnpm/pkg@1.0.0/index.js`, + "clone ".repeat(10_000), + ); + yield* fs.writeFileString(`${nodeModules}/.vite/deps.js`, "cache"); + if (store !== "no-manifest") + yield* fs.writeFileString( + `${nodeModules}/.modules.yaml`, + JSON.stringify({ storeDir, virtualStoreDir: ".pnpm" }), + ); + const counted = [ + ...(yield* fs.readDirectory(worktree)).flatMap((name) => + name === "node_modules" ? [] : [`${worktree}/${name}`], + ), + `${nodeModules}/.vite/deps.js`, + ...(store === "no-manifest" ? [] : [`${nodeModules}/.modules.yaml`]), + ...(store === "same-device" ? [] : [`${nodeModules}/.pnpm/pkg@1.0.0/index.js`]), + ]; + const bytes = (yield* Effect.forEach(counted, (file) => allocatedBytes(fs, file))).reduce( + (sum, size) => sum + size, + 0, + ); + const report = yield* service.runNow; + expect(report.entries[0]).toMatchObject({ outcome: "removed", bytes }); + }), + ), + ); it.live("still removes a worktree when measuring its size fails", () => runCleanupTest( Effect.gen(function* () { diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index a4d1e831d9007..4b82338b1f575 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -32,6 +32,7 @@ import type { PlatformError } from "effect/PlatformError"; import * as Schedule from "effect/Schedule"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; +import { parse as parseYaml } from "yaml"; import * as ServerConfig from "./config.ts"; import * as GitManager from "./git/GitManager.ts"; @@ -55,6 +56,13 @@ const decodeCleanupThread = Schema.decodeUnknownEffect( const decodeCleanupSession = Schema.decodeUnknownEffect( Schema.fromJsonString(OrchestrationV2ProviderSessionJson), ); +// pnpm 11 writes this file as JSON, older versions as YAML. +const decodePnpmModulesManifest = Schema.decodeUnknownEffect( + Schema.Struct({ + storeDir: Schema.String, + virtualStoreDir: Schema.optional(Schema.String), + }), +); const DAY_MS = 86_400_000; // After a settle, the project's settle script needs time to open its @@ -247,14 +255,35 @@ const make = Effect.gen(function* () { ); }); + // pnpm imports packages by clone or reflink when its store shares a device + // with the worktree. Those files have one link but share blocks with the + // store, so removing them frees almost nothing. A store on another device + // forces real copies, which still count. + const sharedPnpmVirtualStore = Effect.fnUntraced( + function* (root: string) { + const nodeModules = path.join(root, "node_modules"); + const text = yield* fs.readFileString(path.join(nodeModules, ".modules.yaml")); + const manifest = yield* decodePnpmModulesManifest(yield* Effect.try(() => parseYaml(text))); + const virtualStore = path.resolve(nodeModules, manifest.virtualStoreDir ?? ".pnpm"); + const [store, virtual] = yield* Effect.all([ + fs.stat(manifest.storeDir), + fs.stat(virtualStore), + ]); + return store.dev === virtual.dev ? virtualStore : null; + }, + Effect.orElseSucceed(() => null), + ); + const measureWorktree = Effect.fn("storageCleanup.measureWorktree")( function* (worktreePath: string) { const root = yield* fs.realPath(worktreePath); + const skipped = yield* sharedPnpmVirtualStore(root); const pending = [root]; let entries = 1; let bytes = 0; while (pending.length > 0) { const target = pending.pop()!; + if (target === skipped) continue; // Effect's stat follows links. Probe with readLink first to get lstat // semantics, including skipping dangling links and directory links. const isLink = yield* fs.readLink(target).pipe( From a1dc856ee61278d0199c36d4bcdef5fdea4d21fe Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 00:34:16 -0700 Subject: [PATCH 002/179] build: upgrade Vite+ to 1.3 and cache typecheck (#22716) Co-authored-by: Claude Opus 5.5 --- apps/desktop/package.json | 1 - apps/desktop/vite.config.ts | 5 + apps/marketing/package.json | 3 +- apps/marketing/vite.config.ts | 15 + apps/web/package.json | 3 +- apps/web/vite.config.ts | 9 + .../core-graph-and-data-model.md | 22 +- .../thread-lineage-and-context-transfer.md | 13 +- knip.jsonc | 4 + package.json | 4 +- pnpm-lock.yaml | 1578 ++++++++--------- pnpm-workspace.yaml | 4 +- vite.config.ts | 5 +- 13 files changed, 797 insertions(+), 869 deletions(-) create mode 100644 apps/marketing/vite.config.ts diff --git a/apps/desktop/package.json b/apps/desktop/package.json index fded4eec9e417..c652ccabc768d 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -7,7 +7,6 @@ "scripts": { "ensure:electron": "node scripts/ensure-electron-runtime.mjs", "start": "node scripts/start-electron.mjs", - "typecheck": "tsc-rs --noEmit", "test": "vp test run --passWithNoTests", "smoke-test": "node scripts/smoke-test.mjs" }, diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 893d6f8e076f1..65a6e7a2892fb 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -43,6 +43,11 @@ export default defineConfig({ dependsOn: ["t3#build"], cache: false, }, + // tsc-rs rewrites the tsbuildinfo it reads, which keeps a task out of the cache. + typecheck: { + command: "tsc-rs --noEmit", + cache: { input: [{ auto: true }, "!*.tsbuildinfo"] }, + }, }, }, pack: [ diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 6eb4a6e41f69a..36dc282e89b47 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -7,8 +7,7 @@ "stage:install-scripts": "node scripts/stage-install-scripts.mjs", "dev": "node scripts/stage-install-scripts.mjs && astro dev", "build": "node scripts/stage-install-scripts.mjs && astro build", - "preview": "astro preview", - "typecheck": "astro check" + "preview": "astro preview" }, "dependencies": { "@t3tools/shared": "workspace:*", diff --git a/apps/marketing/vite.config.ts b/apps/marketing/vite.config.ts new file mode 100644 index 0000000000000..f927357c438df --- /dev/null +++ b/apps/marketing/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vite-plus"; + +// Astro reads astro.config.mjs. This file only configures `vp run` tasks. +export default defineConfig({ + run: { + tasks: { + // `astro check` regenerates .astro/*.d.ts and then reads them, which + // keeps a task out of the cache. Its real inputs are tracked as usual. + typecheck: { + command: "astro check", + cache: { input: [{ auto: true }, "!.astro/**"] }, + }, + }, + }, +}); diff --git a/apps/web/package.json b/apps/web/package.json index a7a7d1c2354f5..8e2171192774b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,7 +8,6 @@ "dev": "vp dev", "build": "vp build", "preview": "vp preview", - "typecheck": "tsc-rs --noEmit", "test": "vp test run --passWithNoTests --project unit --project browser" }, "dependencies": { @@ -92,7 +91,7 @@ "@types/use-sync-external-store": "^0.0.6", "@vercel/config": "^0.3.0", "@vitejs/plugin-react": "^6.1.2", - "@vitest/browser-playwright": "5.0.1", + "@vitest/browser-playwright": "5.0.5", "compression": "^1.8.2", "oxc-transform-react": "~0.152.0", "playwright": "1.60.0", diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index f6fecbfceef35..8158f66367df9 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -181,6 +181,15 @@ const allowedHosts = [".ts.net", ...configuredAllowedHosts]; export default defineConfig(() => { return { + run: { + tasks: { + // tsc-rs rewrites the tsbuildinfo it reads, which keeps a task out of the cache. + typecheck: { + command: "tsc-rs --noEmit", + cache: { input: [{ auto: true }, "!*.tsbuildinfo"] }, + }, + }, + }, assetsInclude: ["**/*.wasm"], plugins: [ devCompressionPlugin(), diff --git a/docs/orchestration-v2/core-graph-and-data-model.md b/docs/orchestration-v2/core-graph-and-data-model.md index ef7dda9c118fa..056f22cfa248a 100644 --- a/docs/orchestration-v2/core-graph-and-data-model.md +++ b/docs/orchestration-v2/core-graph-and-data-model.md @@ -231,7 +231,13 @@ type RunAttempt = { providerTurnId: ProviderTurnId | null; reason: "initial" | "steering_restart" | "retry" | "provider_recovery"; status: - "pending" | "running" | "completed" | "interrupted" | "failed" | "cancelled" | "superseded"; + | "pending" + | "running" + | "completed" + | "interrupted" + | "failed" + | "cancelled" + | "superseded"; startedAt: string | null; completedAt: string | null; }; @@ -374,7 +380,12 @@ type ContextTransfer = { sourceProvider: ProviderKind | null; targetProvider: ProviderKind | null; status: - "pending" | "resolved_native" | "resolved_portable" | "failed" | "consumed" | "superseded"; + | "pending" + | "resolved_native" + | "resolved_portable" + | "failed" + | "consumed" + | "superseded"; resolution: ContextTransferResolution | null; createdBy: "user" | "agent" | "system"; createdAt: string; @@ -468,7 +479,12 @@ type RuntimeRequest = { providerTurnId: ProviderTurnId | null; nativeRequestRef: string | null; kind: - "command" | "file-read" | "file-change" | "dynamic_tool_call" | "user_input" | "auth_refresh"; + | "command" + | "file-read" + | "file-change" + | "dynamic_tool_call" + | "user_input" + | "auth_refresh"; status: "pending" | "resolved" | "expired" | "cancelled"; responseCapability: | { type: "live"; providerSessionId: ProviderSessionId } diff --git a/docs/orchestration-v2/thread-lineage-and-context-transfer.md b/docs/orchestration-v2/thread-lineage-and-context-transfer.md index bf2a117fec207..aafaad5176eab 100644 --- a/docs/orchestration-v2/thread-lineage-and-context-transfer.md +++ b/docs/orchestration-v2/thread-lineage-and-context-transfer.md @@ -79,7 +79,11 @@ Thread relationship describes why a target thread or run exists relative to anot ```ts type ThreadRelationshipKind = - "fork" | "provider_handoff" | "merge_back" | "subagent_spawn" | "subagent_result"; + | "fork" + | "provider_handoff" + | "merge_back" + | "subagent_spawn" + | "subagent_result"; ``` Relationships are product-level and provider-neutral. A fork is not the same thing as a provider-native fork. A provider-native fork is one possible resolution strategy for a fork relationship. @@ -116,7 +120,12 @@ type ContextTransfer = { sourceProvider?: ProviderKind; targetProvider?: ProviderKind; status: - "pending" | "resolved_native" | "resolved_portable" | "failed" | "consumed" | "superseded"; + | "pending" + | "resolved_native" + | "resolved_portable" + | "failed" + | "consumed" + | "superseded"; resolution?: ContextTransferResolution; createdBy: "user" | "agent" | "system"; createdAt: string; diff --git a/knip.jsonc b/knip.jsonc index 31213c07e8d57..7fbfe38259951 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -86,6 +86,10 @@ // reports an entire component module when nothing imports it. "ignoreIssues": { "src/components/ui/*.tsx": ["exports", "nsExports", "duplicates"] }, }, + "apps/marketing": { + // `astro check` runs from the typecheck task in vite.config.ts, which knip does not read. + "ignoreDependencies": ["@astrojs/check"], + }, "apps/mobile": { // Expo loads plugins and the fingerprint config by string; Metro embeds the browser entry in the native WebView. "entry": [ diff --git a/package.json b/package.json index 3e90ec7948a81..53580ef428a2f 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "build:marketing": "vp run --filter @t3tools/marketing build", "build:desktop": "vp run --filter @t3tools/desktop --filter t3 build", "build:resource-monitor": "cargo build --locked --release --manifest-path native/resource-monitor/Cargo.toml", - "typecheck": "vp run -r --concurrency-limit 2 typecheck", - "tc": "vp run -r --concurrency-limit 2 typecheck", + "typecheck": "vp run -r --cache --concurrency-limit 4 typecheck", + "tc": "vp run -r --cache --concurrency-limit 4 typecheck", "lint": "vp lint --report-unused-disable-directives", "knip": "knip --preprocessor ./scripts/knip-schemas.ts", "knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-acp --workspace packages/provider-acp-registry --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --workspace packages/provider-cursor --workspace packages/provider-grok --workspace packages/source-control-core --workspace packages/source-control-azure-devops --workspace packages/source-control-bitbucket --workspace packages/source-control-forgejo --workspace packages/source-control-gitcafe --workspace packages/source-control-github --workspace packages/source-control-gitlab --workspace packages/source-control-testing --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53b971302accc..8c03fc7d0e3a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,8 +160,8 @@ overrides: '@types/ws@^8': 8.18.2 undici@^8: 8.11.2 ws@^8: 8.22.0 - vite: npm:@voidzero-dev/vite-plus-core@1.0.0 - vite-plus: 1.0.0 + vite: npm:@voidzero-dev/vite-plus-core@1.3.0 + vite-plus: 1.3.0 yaml: ^2.9.0 packageExtensionsChecksum: sha256-EowbsmlBFaoO0Ypufw901RnvdyjKG5dcdk2FRbc7Dr0= @@ -222,8 +222,8 @@ importers: specifier: 'catalog:' version: 7.0.2 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/desktop: dependencies: @@ -290,7 +290,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -307,8 +307,8 @@ importers: specifier: 4.3.3 version: 4.3.3 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/marketing: dependencies: @@ -317,7 +317,7 @@ importers: version: link:../../packages/shared astro: specifier: ^7.3.5 - version: 7.3.5(@types/node@24.12.4)(aws4fetch@1.0.20)(idb-keyval@6.2.1)(ioredis@5.11.0)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0) + version: 7.3.5(@types/node@24.12.4)(@volar/typescript@2.4.28)(aws4fetch@1.0.20)(idb-keyval@6.2.1)(ioredis@5.11.0)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0) sharp: specifier: 0.35.4 version: 0.35.4(@types/node@24.12.4) @@ -634,7 +634,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@pierre/trees': specifier: 1.0.0-beta.6 version: 1.0.0-beta.6(@pierre/theme@2.0.0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(shiki@4.2.0) @@ -767,7 +767,7 @@ importers: version: 4.0.5(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.0.5) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@modelcontextprotocol/sdk': specifier: 1.29.0 version: 1.29.0(zod@4.6.5) @@ -811,8 +811,8 @@ importers: specifier: 16.14.1 version: 16.14.1 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) apps/web: dependencies: @@ -1020,13 +1020,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@tailwindcss/vite': specifier: 4.3.3 - version: 4.3.3(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)) '@tanstack/router-plugin': specifier: ^1.161.0 - version: 1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)) + version: 1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)) '@types/compression': specifier: ^1.8.1 version: 1.8.1 @@ -1050,10 +1050,10 @@ importers: version: 0.3.0 '@vitejs/plugin-react': specifier: ^6.1.2 - version: 6.1.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0) + version: 6.1.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0) '@vitest/browser-playwright': - specifier: 5.0.1 - version: 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.1) + specifier: 5.0.5 + version: 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) compression: specifier: ^1.8.2 version: 1.8.2 @@ -1067,11 +1067,11 @@ importers: specifier: 4.3.3 version: 4.3.3 vite: - specifier: npm:@voidzero-dev/vite-plus-core@1.0.0 - version: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 + version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) infra/relay: dependencies: @@ -1104,7 +1104,7 @@ importers: version: link:../../packages/shared alchemy: specifier: 2.0.0-beta.80 - version: 2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(c218479f13e455b978e1beb907da1b95) + version: 2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(d214a55921b9b1c12454f5ddcf93cf1a) drizzle-orm: specifier: 1.0.0-rc.5-ab785fc version: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@react-native/metro-config@0.88.0(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(valibot@1.2.0(typescript@7.0.2))(zod@4.4.3) @@ -1120,7 +1120,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1131,11 +1131,11 @@ importers: specifier: 'catalog:' version: 7.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@1.0.0 - version: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 + version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) oxlint-plugin-t3code: dependencies: @@ -1151,10 +1151,10 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/client-runtime: dependencies: @@ -1197,13 +1197,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/three': specifier: 0.180.0 version: 0.180.0 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/contracts: dependencies: @@ -1213,10 +1213,10 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/effect-acp: dependencies: @@ -1232,13 +1232,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/effect-codex-app-server: dependencies: @@ -1254,13 +1254,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-acp: dependencies: @@ -1285,7 +1285,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1293,8 +1293,8 @@ importers: specifier: workspace:* version: link:../effect-acp vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-acp-registry: dependencies: @@ -1322,7 +1322,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1333,8 +1333,8 @@ importers: specifier: workspace:* version: link:../effect-acp vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-core: dependencies: @@ -1353,13 +1353,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-cursor: dependencies: @@ -1390,7 +1390,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1398,8 +1398,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-grok: dependencies: @@ -1424,7 +1424,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1435,8 +1435,8 @@ importers: specifier: workspace:* version: link:../effect-acp vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-muse: dependencies: @@ -1461,7 +1461,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1469,8 +1469,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-opencode: dependencies: @@ -1504,7 +1504,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1512,8 +1512,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-pi: dependencies: @@ -1535,7 +1535,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1543,8 +1543,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/provider-testing: dependencies: @@ -1630,7 +1630,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -1641,8 +1641,8 @@ importers: specifier: 'catalog:' version: 2.0.2 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-azure-devops: dependencies: @@ -1667,7 +1667,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1675,8 +1675,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-bitbucket: dependencies: @@ -1698,13 +1698,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-core: dependencies: @@ -1720,13 +1720,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-forgejo: dependencies: @@ -1748,7 +1748,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1756,8 +1756,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-gitcafe: dependencies: @@ -1779,7 +1779,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1787,8 +1787,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-github: dependencies: @@ -1816,7 +1816,7 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1824,8 +1824,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-gitlab: dependencies: @@ -1844,7 +1844,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -1852,8 +1852,8 @@ importers: specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/source-control-testing: dependencies: @@ -1891,13 +1891,13 @@ importers: version: 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages/tailscale: dependencies: @@ -1910,13 +1910,13 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) scripts: dependencies: @@ -1950,7 +1950,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.5 - version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/plist': specifier: 'catalog:' version: 3.0.5 @@ -1967,8 +1967,8 @@ importers: specifier: npm:typescript@~6.0.3 version: typescript@6.0.3 vite-plus: - specifier: 1.0.0 - version: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + specifier: 1.3.0 + version: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) packages: @@ -4900,8 +4900,8 @@ packages: cpu: [x64] os: [win32] - '@oxc-project/runtime@0.151.0': - resolution: {integrity: sha512-fNuliiqGTseerW2FTbxq6UOQavKL/NvNh9K3peJZZVNi1NbM4Ehscwp3MX9nu+9bfbSfmPUEivF779XQU5exiw==} + '@oxc-project/runtime@0.154.0': + resolution: {integrity: sha512-cBQNGoh659BNgvA8OC/e4BNDtFkv03dJaYdkp2adgOQNtISNAPQ+nB4S8XvWtNk7Xl+0g+7aa+iRGR25Xycd1Q==} engines: {node: ^20.19.0 || >=22.12.0} '@oxc-project/types@0.127.0': @@ -4919,12 +4919,12 @@ packages: '@oxc-project/types@0.148.0': resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==} - '@oxc-project/types@0.151.0': - resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} - '@oxc-project/types@0.153.0': resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} + '@oxc-project/types@0.154.0': + resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] @@ -5150,276 +5150,276 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.70.0': - resolution: {integrity: sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw==} + '@oxfmt/binding-android-arm-eabi@0.73.0': + resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.70.0': - resolution: {integrity: sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg==} + '@oxfmt/binding-android-arm64@0.73.0': + resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.70.0': - resolution: {integrity: sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA==} + '@oxfmt/binding-darwin-arm64@0.73.0': + resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.70.0': - resolution: {integrity: sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w==} + '@oxfmt/binding-darwin-x64@0.73.0': + resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.70.0': - resolution: {integrity: sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA==} + '@oxfmt/binding-freebsd-x64@0.73.0': + resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.70.0': - resolution: {integrity: sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': + resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.70.0': - resolution: {integrity: sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA==} + '@oxfmt/binding-linux-arm-musleabihf@0.73.0': + resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.70.0': - resolution: {integrity: sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA==} + '@oxfmt/binding-linux-arm64-gnu@0.73.0': + resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.70.0': - resolution: {integrity: sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg==} + '@oxfmt/binding-linux-arm64-musl@0.73.0': + resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.70.0': - resolution: {integrity: sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA==} + '@oxfmt/binding-linux-ppc64-gnu@0.73.0': + resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.70.0': - resolution: {integrity: sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.73.0': + resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.70.0': - resolution: {integrity: sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g==} + '@oxfmt/binding-linux-riscv64-musl@0.73.0': + resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.70.0': - resolution: {integrity: sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg==} + '@oxfmt/binding-linux-s390x-gnu@0.73.0': + resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.70.0': - resolution: {integrity: sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ==} + '@oxfmt/binding-linux-x64-gnu@0.73.0': + resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.70.0': - resolution: {integrity: sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA==} + '@oxfmt/binding-linux-x64-musl@0.73.0': + resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.70.0': - resolution: {integrity: sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA==} + '@oxfmt/binding-openharmony-arm64@0.73.0': + resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.70.0': - resolution: {integrity: sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q==} + '@oxfmt/binding-win32-arm64-msvc@0.73.0': + resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.70.0': - resolution: {integrity: sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA==} + '@oxfmt/binding-win32-ia32-msvc@0.73.0': + resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.70.0': - resolution: {integrity: sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg==} + '@oxfmt/binding-win32-x64-msvc@0.73.0': + resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@7.0.2003': - resolution: {integrity: sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==} + '@oxlint-tsgolint/darwin-arm64@7.0.2004': + resolution: {integrity: sha512-uJk3ttju7Lz+Nv8WG4CsLyewoL1qf/Ny8S8OksJ7Bm/F14Tc9gMfW6arM1PCc57IzH/X/I79YsCWTgxX1FKibQ==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@7.0.2003': - resolution: {integrity: sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==} + '@oxlint-tsgolint/darwin-x64@7.0.2004': + resolution: {integrity: sha512-0BFfHTzDxNDFPcu0UZAXT6CyCJHAXM0dZveyDb4KEHa2qF/DSnOKOrmvK/wFuFol9kvijbGM9JfuOsor+zWXRw==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@7.0.2003': - resolution: {integrity: sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==} + '@oxlint-tsgolint/linux-arm64@7.0.2004': + resolution: {integrity: sha512-TAehQc+qdRXyDN3nl/r/3/oPrBVrbtrDeGMkFyG6mf+XphpoJ2lAMVFLclyvrVZCw02RbKROjyw8h72XwQQHcw==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@7.0.2003': - resolution: {integrity: sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==} + '@oxlint-tsgolint/linux-x64@7.0.2004': + resolution: {integrity: sha512-LPunZvm6ZbWPfHMAyfZf9ci3HHvNEzLejO8zdqO3GoVGoSEHqqufHUD7+OgeatVSaJP7uXDJ2HaNOHIoH7go4Q==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@7.0.2003': - resolution: {integrity: sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==} + '@oxlint-tsgolint/win32-arm64@7.0.2004': + resolution: {integrity: sha512-FVWplzDImWhiuawnIZVETaQecv4GWom6Jv4Fx2xj7hhXXDZ5okEgiw1gTCImHTIQsi2ycC5oSxVwrn69bMh7iw==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@7.0.2003': - resolution: {integrity: sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==} + '@oxlint-tsgolint/win32-x64@7.0.2004': + resolution: {integrity: sha512-7PsxuUx11Ry9DYTWcljTzRbmZY9B6O9fFFfZV+aodQEMy6pfRT3fhoQRv9PtGN/89z1pPz3nFKrsFm6WqNtX7w==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.85.0': - resolution: {integrity: sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==} + '@oxlint/binding-android-arm-eabi@1.88.0': + resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.85.0': - resolution: {integrity: sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==} + '@oxlint/binding-android-arm64@1.88.0': + resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.85.0': - resolution: {integrity: sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==} + '@oxlint/binding-darwin-arm64@1.88.0': + resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.85.0': - resolution: {integrity: sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==} + '@oxlint/binding-darwin-x64@1.88.0': + resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.85.0': - resolution: {integrity: sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==} + '@oxlint/binding-freebsd-x64@1.88.0': + resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.85.0': - resolution: {integrity: sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==} + '@oxlint/binding-linux-arm-gnueabihf@1.88.0': + resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.85.0': - resolution: {integrity: sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==} + '@oxlint/binding-linux-arm-musleabihf@1.88.0': + resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.85.0': - resolution: {integrity: sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==} + '@oxlint/binding-linux-arm64-gnu@1.88.0': + resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.85.0': - resolution: {integrity: sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==} + '@oxlint/binding-linux-arm64-musl@1.88.0': + resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.85.0': - resolution: {integrity: sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==} + '@oxlint/binding-linux-ppc64-gnu@1.88.0': + resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.85.0': - resolution: {integrity: sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==} + '@oxlint/binding-linux-riscv64-gnu@1.88.0': + resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.85.0': - resolution: {integrity: sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==} + '@oxlint/binding-linux-riscv64-musl@1.88.0': + resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.85.0': - resolution: {integrity: sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==} + '@oxlint/binding-linux-s390x-gnu@1.88.0': + resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.85.0': - resolution: {integrity: sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==} + '@oxlint/binding-linux-x64-gnu@1.88.0': + resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.85.0': - resolution: {integrity: sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==} + '@oxlint/binding-linux-x64-musl@1.88.0': + resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.85.0': - resolution: {integrity: sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==} + '@oxlint/binding-openharmony-arm64@1.88.0': + resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.85.0': - resolution: {integrity: sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==} + '@oxlint/binding-win32-arm64-msvc@1.88.0': + resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.85.0': - resolution: {integrity: sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==} + '@oxlint/binding-win32-ia32-msvc@1.88.0': + resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.85.0': - resolution: {integrity: sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==} + '@oxlint/binding-win32-x64-msvc@1.88.0': + resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -5428,8 +5428,8 @@ packages: resolution: {integrity: sha512-titLmukUt/h8ho7Svlf0xSBjoy2ccZKrXjpXpZCj+v6V4CJccC2KyP45BLSCMx8YIpifMyiDyUptM4+5sruKbQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@oxlint/plugins@1.79.0': - resolution: {integrity: sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg==} + '@oxlint/plugins@1.88.0': + resolution: {integrity: sha512-G2xbfWTrbqYRhR/vfeKNMQf/sS3/ti0iKJAAkRxHuXWZH/hne/dyIb9QKk5LuR6cJLARxsL6FMoAYmfspd74MQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@peculiar/asn1-schema@2.8.0': @@ -6335,8 +6335,8 @@ packages: resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} engines: {node: '>=20.19'} - '@testing-library/dom@10.4.1': - resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} engines: {node: '>=18'} '@testing-library/user-event@14.6.7': @@ -6863,26 +6863,26 @@ packages: oxc-transform-react: optional: true - '@vitest/browser-playwright@5.0.1': - resolution: {integrity: sha512-BlknN81ZeoCyP5CSm5u7+1BTlS7d0cDU5DfndNSjEC3SmbzOESGrp+DQpNhQmTlggawWc+yKVEM03mM/aBynqg==} + '@vitest/browser-playwright@5.0.5': + resolution: {integrity: sha512-lcdThFUUcMV9mGGV1SUiZXPxfjgW4CssA7o3STxzh8WK0tT2r526kc12tZIBRZfoLMwkhxjLmMXkWXONzdsDQw==} peerDependencies: playwright: '*' - vitest: 5.0.1 + vitest: 5.0.5 - '@vitest/browser-preview@5.0.1': - resolution: {integrity: sha512-NagNnZD6A8ccl+pycLWX2b/e9YJtH5NuDUyiiadQjptMRuZVBAB9r9I3itdKm6exxtcl+rOTv2rWt6vu5uOm1A==} + '@vitest/browser-preview@5.0.5': + resolution: {integrity: sha512-ViNg4UlomGSJHtnnr7Txm+55P7/aAMMoS+1WYnWjp5w5tzf7YEUAICcs2sb1uMovEnC8el/8zUfkWZ8+7fPCKA==} peerDependencies: - vitest: 5.0.1 + vitest: 5.0.5 - '@vitest/browser@5.0.1': - resolution: {integrity: sha512-s2UroEhP2BZPoen1qrXXTtVqnUcs6y2sxMLGCZ8tZDkH2iM/6tTC2GVTPCgF+oVF3X5M6wC/X2O0MvvoHNmH0Q==} + '@vitest/browser@5.0.5': + resolution: {integrity: sha512-IDMoZX/iZ8rkilw7weaSgN8tJ7iZ9hUv0wYoKXyiyjWjWFwLSWAY707DcVTgxTYOudWC1E3HA9LYn7kVa9a7uQ==} peerDependencies: - vitest: 5.0.1 + vitest: 5.0.5 - '@vitest/mocker@5.0.1': - resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==} + '@vitest/mocker@5.0.5': + resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: - msw: ^2.4.9 + msw: ^2.4.9 || ^3.0.0 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: @@ -6890,30 +6890,33 @@ packages: vite: optional: true - '@vitest/pretty-format@5.0.1': - resolution: {integrity: sha512-6guWwj5d9bguuefTOvJoq387tfpkzSv554YdUEGzjJH2PnnmvzTLQ1UQSuAk5wBFVhf2CUmy/S/palOcb6dmpA==} + '@vitest/pretty-format@5.0.5': + resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} - '@vitest/snapshot@5.0.1': - resolution: {integrity: sha512-aEW4fuQVg1i3fnC781qvwxC5QNptUvb+JjIUfX1WuWJqkjCgpiU8+I89mMcJmxOPLdaGAwEPFFVk02rGKYexvw==} + '@vitest/snapshot@5.0.5': + resolution: {integrity: sha512-dHyFElWMQy0wFBGrIaPPeUWfJFh+jAUrv46ImIJGd8CADdKV3mlrAu3FyD0wesdxELFCs9M807e040Kba/XKtA==} - '@vitest/spy@5.0.1': - resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==} + '@vitest/spy@5.0.5': + resolution: {integrity: sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==} - '@vitest/ui@5.0.1': - resolution: {integrity: sha512-7PvQu/X9/pQoHYfNLAYL22qsD4/+sx2k7zpUA7XvjW0sc40r3/r0lGZ2fsEyOHMuO8Q1KjiO1QQVjJdnWUy/WQ==} + '@vitest/ui@5.0.5': + resolution: {integrity: sha512-dCoGUOdgmCaSB2g+WQ/spPCBkytAWdNUkFSXY4z+eYp2d8JEOY8v0uDRaJTG9qf/kTlTVZ4CuYMYMz/f1HS/qQ==} peerDependencies: - vitest: 5.0.1 + vitest: 5.0.5 - '@vitest/utils@5.0.1': - resolution: {integrity: sha512-E9+yEA+jsfaoxZcUHFzEqUrQcoNh2EwrPT5efIqkUPUwD5Ua2Li9BRWaYeRwvzvdLgTSVrre8oKNeyrfg7KkdQ==} + '@vitest/utils@5.0.5': + resolution: {integrity: sha512-/rBu7gzLDQI/SC79Tn98b5DwOgaHU/Gt4I9OVeP3YofzNaYtbImRCOJQmT1S6vOJg9gEIlWC+r3QxKkjuSa1nQ==} - '@voidzero-dev/vite-plus-core@1.0.0': - resolution: {integrity: sha512-GHCd6d7h3JVojI/yxe/EAHwX5gn+OI7G932VBnYoTiwATLJrvHOs+okx7Ek2LGEJ57NJRENwCFgVYefjwPLfFA==} + '@voidzero-dev/vite-plus-core@1.3.0': + resolution: {integrity: sha512-Bk/LBwNrxoB8XbGUtGJE3r+U9P4puvdZ6UH36ekygE1J+on/ytwM/yBgQwBnvmX6/MVax+2xxj8wk0N/KkyNeA==} engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 '@types/node': 24.12.4 + '@typescript/native-preview': '*' '@vitejs/devtools': ^0.7.1 + '@volar/typescript': ~2.4.0 + '@vue/language-core': ~3.2.0 || ~3.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -6927,14 +6930,21 @@ packages: typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 unplugin-unused: '>=0.5.0' unrun: '*' + vue-tsc: ~3.2.0 || ~3.3.0 yaml: ^2.9.0 peerDependenciesMeta: '@arethetypeswrong/core': optional: true '@types/node': optional: true + '@typescript/native-preview': + optional: true '@vitejs/devtools': optional: true + '@volar/typescript': + optional: true + '@vue/language-core': + optional: true esbuild: optional: true jiti: @@ -6961,57 +6971,59 @@ packages: optional: true unrun: optional: true + vue-tsc: + optional: true yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@1.0.0': - resolution: {integrity: sha512-lR+0rHOCYQTUo4Un0Sy14Xi1NBqoPXRnhsRyc0Joe53fomZ+wPtgErk15tkvWQdSLGNOr8hDDWJKo/kzI0Y37w==} + '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': + resolution: {integrity: sha512-BF4W+UwGxUkHPrH2pZLhafJfkJT20WZIbMzZgZLGQ44LPpZPX8Sb+qtblUnXUkkCzTqad4XtSPgiLKglvZkNQQ==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@1.0.0': - resolution: {integrity: sha512-MW3YnWpYP6wxvpd5KLFYTdtHTcAVnN0mVoLrmA9KCKWfOzO8nanr8hpuyEIdLMQT3r/7VNKRvgROI0CZZDP9/A==} + '@voidzero-dev/vite-plus-darwin-x64@1.3.0': + resolution: {integrity: sha512-vLZTk0r+s3e7mycj4SGBbcpK+xyM8a86hXQZtQbJyW7xrGJzsavISy7/m4gGZpWN32Hl4Tmx9V/ffeaoXLkRdg==} engines: {node: '>=20.0.0'} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.0.0': - resolution: {integrity: sha512-n4onkD4oT+g4jLj9rR37TM4i2IbaYvIiU68p1A/5KrwnIHzbv2maO0BqBYdIRnBy1Mil8pIx9HsT8riWCjZeTw==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': + resolution: {integrity: sha512-H43Dj9RkEyjOFXxTk3DLTa6hqmHhxUN9vbbqNdwQoc3/TPNhC1zsLZy3qgUFbYGLdS7M9jICCqFzj5GDSYrhIw==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@1.0.0': - resolution: {integrity: sha512-EZfgINa0FL/eIsLkL3MfQz7+Dc4VDAeIrf1UZDlitt//8NIOqI6nVMPnIFzMZThaIncIose+cVWsEkKLAKMM/A==} + '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': + resolution: {integrity: sha512-Se2GlvzUZOvFy+5hoQHoG7LFeiplKs+EJwaskayCSLwZAP+e6/LTK7EK+8OcdMpmdsLPtZoTJHkIjSzrQowJwQ==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@1.0.0': - resolution: {integrity: sha512-R6+/oVLCSEvatOG1utYo0d0I++QPjWuRt2moD21+UUu2mQ8VyZHaaQLgoxrQG9shA9o0RBDmwxhGUXL20Rk1FA==} + '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': + resolution: {integrity: sha512-moxojk6zmog2SaLE4/cG75acE6X3cjR0/52Ba8OG2ct0r+dv55VSE0kellAuxS6o9VHy7TBhkIpwpFpQ34rnAA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@1.0.0': - resolution: {integrity: sha512-B1Ottn22hn9RZ6XIVrWCXAAMfCOQRJ4KWoKbPL4JxC+UgvEiY9kC1ZFPqEAI89zIbW8eLdP3BeT14NQ0h1rtxw==} + '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': + resolution: {integrity: sha512-CykxH008Dy9u20Myki9/UIqq3Dbo1ZMqa098aS6AtWp3QKyEKzSLPB3jKDUklgmu83xiFe5BpPA+33oF0yNY7A==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.0.0': - resolution: {integrity: sha512-XXK6N5xMkGVIcqMaVDjqDh44z0Z00DA8dYn2BWUkFOQflUU1Z3IWt+bPm/1Y7LjkwBWj5qD2zsQY1ANDCeX9QA==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': + resolution: {integrity: sha512-l61S9b8aeiDpi8wPRfz4HonqRkLpt/OVYs0sCskvlXYSW/L8qfLOtuWH/xpF8xDjMIfBefKejBVfU1UlRax5Tw==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@1.0.0': - resolution: {integrity: sha512-XabdwdAsJ9QqEVSpSOuiiyJjiEwbwOspGahei9svQUGjs7e8MPuj4y/ltBHJZpyCBdZI6fhlc7Xaps9bUtl7Og==} + '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': + resolution: {integrity: sha512-b6Ng8YezA6JA3adQBNA+8Mn26+OX4fw/tsPfnk//5Cx+1XFDZoAxWYXtGzc0yUQPEKnoGtfw/0OUvcibg61yYg==} engines: {node: '>=20.0.0'} cpu: [x64] os: [win32] @@ -7053,164 +7065,74 @@ packages: resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==} engines: {node: '>=14.6'} - '@yuku-codegen/binding-android-arm64@0.9.5': - resolution: {integrity: sha512-jrOY5WM+AaAqkv51fHP1x28ifto4WgcZVzodLUyMU1jMWn5Sq+VciRdk/n8E0Ey5w4p4cWWE+m5GfXWOYh7Kzw==} - cpu: [arm64] - os: [android] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-darwin-arm64@0.9.5': - resolution: {integrity: sha512-4O4lkCQIzPZGjiNB1GORSI6hBECbiiSBS/APZXPvNKYH+nhY4uuqv03LNXA+SET3hoBjvr95P5rIhY8KQaQUBA==} - cpu: [arm64] - os: [darwin] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-darwin-x64@0.9.5': - resolution: {integrity: sha512-9EvoUO0SEhD6/d8VHdWuPerepPMSR1y84+UEgz8Un1Ope14Oe7xMvePpEQLTLovoIFZ8Zg3iZ8z8E11pZMqC3g==} - cpu: [x64] - os: [darwin] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-freebsd-x64@0.9.5': - resolution: {integrity: sha512-HqT78WwgHTmp8lwujoUa9CrIortX4DdpuiVC18ZPSGvuuJf4ylpIEI6QrQEM78Zwz3muhAWAOXZ5irdiYY+AyA==} - cpu: [x64] - os: [freebsd] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-arm-gnu@0.9.5': - resolution: {integrity: sha512-QJXwIW6Ms3QIawbcBIedmrmXnfdpuAOjZJ/eAABq5XTzWvSxZ7lutu9W5yIHahlaTaFnBo7ikrVMD1szLTpPUw==} - cpu: [arm] - os: [linux] - libc: [glibc] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-arm-musl@0.9.5': - resolution: {integrity: sha512-eHbFy3IHGb+IYarrYwAo0yWSEQV3eAmEn6VrsKA6I1Wy1YPJxWqSJlV69JhqsHtJuJlljUIF3ex0y46yaKIu9w==} - cpu: [arm] - os: [linux] - libc: [musl] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': - resolution: {integrity: sha512-ByoJMbySTaDhjAXSu8q6Lh7HKg3YoesXpcT72aYk0Aiw4PCznmY4ybpLTq0RCvp0RIPhFm/6yECFiGBwyCC1nw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-arm64-musl@0.9.5': - resolution: {integrity: sha512-gD9vfXIoBw1toSxhO9rgmSu/FEfy3PMznJAxVjIuH8DrWEiDKXmJO0pJKfj1Ltbe/TmtWVZR+TjISqeSIGQzMg==} - cpu: [arm64] - os: [linux] - libc: [musl] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-x64-gnu@0.9.5': - resolution: {integrity: sha512-BARvdnvqMGjOr5Iel2JH+9H5vAIE0R6H0Z2fsT02xrvmI/1ZXNB8lkuX+ZGevPhZb3zJ9WeC/R8JZstrsUOK5g==} - cpu: [x64] - os: [linux] - libc: [glibc] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-linux-x64-musl@0.9.5': - resolution: {integrity: sha512-x8flcevS1fbb7ESrmpOY/pON4eInSaE/7Ktjqx2udOE2W33BNSZmJuwpyUgo54AoHNqrcaM7szqydyJn5fNvew==} - cpu: [x64] - os: [linux] - libc: [musl] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-win32-arm64@0.9.5': - resolution: {integrity: sha512-KOL/rBatWqH4ZpCNoF8ZtSNdOJbAxBJLmk/VeRciepGROPTbPum1A9t67GpFgOU7qrkUWlPJdJ+KHKbvbmOt+w==} - cpu: [arm64] - os: [win32] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-codegen/binding-win32-x64@0.9.5': - resolution: {integrity: sha512-FxENahEjWSan59Syh/us/Kf4wNq8qrJLFJ3R2N4Oiwtb6yNdz/rWLNTIoNSssnsp7IoWJdUP6o/Z8ppg7lXMcg==} - cpu: [x64] - os: [win32] - deprecated: yuku-codegen is pure JavaScript since 0.14 - - '@yuku-parser/binding-android-arm64@0.9.5': - resolution: {integrity: sha512-A2JCFCSHfnficqYEw4Iujpx7XrkMM3UfFcgJFmYpZSo9zM4nyhmdIIE0FogSduuU60lhM0/UcfuUBXIVNBMlGQ==} + '@yuku-core/binding-android-arm64@0.19.0': + resolution: {integrity: sha512-wdL2XmTiZixAOaHk9Z0JJZ8u9Qm8d333Zjpc6gaxXFX+UR5BlsfxZ7a2+2Rm71pamppsPYGUGFq2oVLbh6DISw==} cpu: [arm64] os: [android] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-darwin-arm64@0.9.5': - resolution: {integrity: sha512-3PiyU+Eare4YuKaQ22N98/yAiROPY5o/NQJHraICzDvk4pgS+m+bgLKOvkGBRn33OnV95Vdv1Mn38b+MQHpULQ==} + '@yuku-core/binding-darwin-arm64@0.19.0': + resolution: {integrity: sha512-7sNJa6XJhyetnTy64jw3NEi4YgUcezHk1G5GH9FSvXAuC8wIlbaOwT9lgq5qo5sspjrZJQlBR1TWflP49uRpMw==} cpu: [arm64] os: [darwin] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-darwin-x64@0.9.5': - resolution: {integrity: sha512-blFMAFI7AInI83XaiOF8cIeiRM46Nz9EfpZtZPRLbKxSA9aAr5v8aHYpmfN6NoyXxAv/10pwS4gsAuc1W6fy5Q==} + '@yuku-core/binding-darwin-x64@0.19.0': + resolution: {integrity: sha512-pTC7dYEc59isdyizc3nsuyFNBu2LU5wMYZ1be6UMr59PY/AJNtO1aBLWV0MgL/z7C1nv/2sX4TNjGgKLoeevJw==} cpu: [x64] os: [darwin] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-freebsd-x64@0.9.5': - resolution: {integrity: sha512-TsNuL4qsZdO0tHp5GW43Y5fHeLPpPHA675wdcPNTcdq2ZO5AvsQWFFoiDg8CH4oBktfsQ9tdvKhjLnLYwKvp4g==} + '@yuku-core/binding-freebsd-x64@0.19.0': + resolution: {integrity: sha512-xrZjF6XOEwyLOiDIfrwDdRCS6ghxcJeirNC1KivQhVcUwlRonaU79S3/Uiyx3lBylPnT8qKJ3/aMT+H52nGTJQ==} cpu: [x64] os: [freebsd] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-arm-gnu@0.9.5': - resolution: {integrity: sha512-b0afYK5gHeV8RdmOcqAlgM8ONsye4cax4DMnIBaoJyPMd1UTGvTbpkqQcPVdhmHlcfcGVGMV1laeVovlr/dscw==} + '@yuku-core/binding-linux-arm-gnu@0.19.0': + resolution: {integrity: sha512-0OyIH53C0I1CbADHn9FSdCfX5qbpeLwJhmOVhonFJNYheybOnkSJ50qGMfTJIb8y5zrwqvLs7FVBvz6Fk8maZg==} cpu: [arm] os: [linux] libc: [glibc] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-arm-musl@0.9.5': - resolution: {integrity: sha512-fD3lKzl+r6j6n8DwiMY53qnh5DLqD8KJjCp+NbVud54Nnh3Q9Wprqss3YzyMHP3NSJk663Wlg1E1X5qOuFVFig==} + '@yuku-core/binding-linux-arm-musl@0.19.0': + resolution: {integrity: sha512-z/iPfkCOCSXE34VXNt0uP1ORSW+4mEa9b5CiJcWZeqZH08g49bFU/jRM2T04f+Wpw5mJ6HIzNBiojxeAbrHjeg==} cpu: [arm] os: [linux] libc: [musl] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-arm64-gnu@0.9.5': - resolution: {integrity: sha512-aRU/aCphV1MWCl/lvI6NX8LgucHQ68Fx+vz7NBb/5MEr2EuzCxiPOQqeFcMdWh+2AED/p0AvAREch0c+cSnlhA==} + '@yuku-core/binding-linux-arm64-gnu@0.19.0': + resolution: {integrity: sha512-jLNTi+gzaRwBJ+gwhCJ+rDb5dmx/8hEBx93UWd7hjMSqPTL1D1wvVSvXVAldV5kALvju7b0/m51EHEhs4+I1fA==} cpu: [arm64] os: [linux] libc: [glibc] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-arm64-musl@0.9.5': - resolution: {integrity: sha512-5+Guro0l8H473YXlEjVNBRLN/IbPbJdnQh1zo0OLt49xxnON+XMJRRaEyTOTfkn9QSRg0+BhEKGR4W9Gu2uZJQ==} + '@yuku-core/binding-linux-arm64-musl@0.19.0': + resolution: {integrity: sha512-YwyPIdW/rN2Fmv0MTFWi47FaB3F9g+2b+sJISyAw83Yd60RWH4T3Nx6XkAHHVN+uKJNBAd6So7/BVihSiFYvuQ==} cpu: [arm64] os: [linux] libc: [musl] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-x64-gnu@0.9.5': - resolution: {integrity: sha512-pwwSyV9q+GlvzSXlsMZBMlgk22L5bud714/vqZCdeUTivzeUVltQzUaf3IQXOGXdpc59JYTeuMCtbGquaAUoCA==} + '@yuku-core/binding-linux-x64-gnu@0.19.0': + resolution: {integrity: sha512-/vskaAXVX3ESR43FiTdfiYS7z1b9uoQJwVQ/0RgUHgN2sWoAyCZs+aX4CgU7eKGIHTVfp9VFwv+c+2XchYiEtQ==} cpu: [x64] os: [linux] libc: [glibc] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-linux-x64-musl@0.9.5': - resolution: {integrity: sha512-z18j6JN3lBHH8vzN7gG1M8fI0nlgItSfnC9PfbmUbt97iHViKfw2iA2G9fGwRMe5LyPRZBrejIlapbygPbpdaw==} + '@yuku-core/binding-linux-x64-musl@0.19.0': + resolution: {integrity: sha512-FuXL5igeSKib9Rye02TdH+D6SQezTyGxIATMu1Qsn/QywrzwpGl35OLpS2C1x5vpGLeVE2XgiLd5mVPFHmIglQ==} cpu: [x64] os: [linux] libc: [musl] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-win32-arm64@0.9.5': - resolution: {integrity: sha512-s6Gwttb1dQvtPX6Bgkw+UPC9IO3UBDXc6Zezog8MMgvu3UfJBBB9TQqKBX/2quu0Eyh+lLSAyNlIyyecaagUPg==} + '@yuku-core/binding-win32-arm64@0.19.0': + resolution: {integrity: sha512-YuJAe7zEKT1Xa7LuHlhGJpPnYQeVtvo0rZvNjJRqkH0hQGOw4C8QeP30Tg5VKGzZfo2x8CjhjEhKt4TmLwfmdg==} cpu: [arm64] os: [win32] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-parser/binding-win32-x64@0.9.5': - resolution: {integrity: sha512-FrERt9YWatY3bJfSUdi4YWY+6iQcyo3MzCC821BxlWM5TFZEHijnpz/bknR79VHlXY/9CvXz8ZlaAGPsTtN3nw==} + '@yuku-core/binding-win32-x64@0.19.0': + resolution: {integrity: sha512-o8iY/mXwlRkedp0LNTr4+TEfXTPQwoNZmv6k+1WIAd1ZXEn8xJJTBCtB0osaVvdZo/x/cNmV16UrWrrGpM5Mvw==} cpu: [x64] os: [win32] - deprecated: yuku-parser runs on yuku-core since 0.14 - '@yuku-toolchain/types@0.9.5': - resolution: {integrity: sha512-KiuLNNgX9uNealaWAR+G3/cMXnRk9x4TY2EkYe/KIag+UPdwiA0RRf1hr1WAxzTP8KGzCTkqUdLPvqh32sEO3w==} + '@yuku-toolchain/types@0.19.0': + resolution: {integrity: sha512-tFkNbsP4WXX9UMUC5syJGdK9dPMLK0PPaIuXCcCYH75sZ8MrebfBZ2XvCILRTw8AgLivSSCfDa4R67iYBDa4Kg==} '@yuuang/ffi-rs-android-arm64@1.3.2': resolution: {integrity: sha512-eDYLT0kVBkp7e2BwdRDmt6N1rkeDPUHDefk3ZX0/nok+GLsqfy1WBoSL3Yg7HVXN1EyW8OBVc2uK8Zq8HbmaSA==} @@ -7754,8 +7676,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} chalk@2.4.2: @@ -9762,6 +9684,9 @@ packages: magic-string@1.4.1: resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + magicast@0.5.5: resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} @@ -10337,10 +10262,6 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - obug@2.2.1: - resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} - engines: {node: '>=12.20.0'} - obug@3.0.0: resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} engines: {node: '>=12.20.0'} @@ -10417,30 +10338,33 @@ packages: resolution: {integrity: sha512-CAx+SKDU4uSOmQnyn7daMj/v/h8tF3fnaUMi4yImKAARPnXV0BieHe/qw1LTcK4KlnIuAhpfUJfSfNKIBh8HqQ==} engines: {node: ^20.19.0 || >=22.12.0} - oxfmt@0.70.0: - resolution: {integrity: sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw==} + oxfmt@0.73.0: + resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: + '@astrojs/compiler-rs': ^0.4.0 || ^0.5.0 svelte: ^5.0.0 - vite-plus: 1.0.0 + vite-plus: 1.3.0 peerDependenciesMeta: + '@astrojs/compiler-rs': + optional: true svelte: optional: true vite-plus: optional: true - oxlint-tsgolint@7.0.2003: - resolution: {integrity: sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==} + oxlint-tsgolint@7.0.2004: + resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==} hasBin: true - oxlint@1.85.0: - resolution: {integrity: sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==} + oxlint@1.88.0: + resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=7.0.2001' - vite-plus: 1.0.0 + oxlint-tsgolint: '>=7.0.2003' + vite-plus: 1.3.0 peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -10643,10 +10567,6 @@ packages: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} - postcss@8.5.28: - resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.30: resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} engines: {node: ^10 || ^12 || >=14} @@ -11331,9 +11251,6 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -11409,9 +11326,6 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -11601,8 +11515,8 @@ packages: tiny-typed-emitter@2.1.0: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} - tinybench@6.1.4: - resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} engines: {node: '>=20.0.0'} tinyclip@1.0.3: @@ -11613,10 +11527,6 @@ packages: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} - tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -11625,12 +11535,12 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinypool@2.1.2: - resolution: {integrity: sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==} + tinypool@2.2.0: + resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} engines: {node: '>=14.0.0'} tldts-core@7.4.2: @@ -12000,18 +11910,21 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-plus@1.0.0: - resolution: {integrity: sha512-2ezzBWt+AVO+I2F9Cpb2Of0J12Lat35kWZ/FRL2monMnKZuACKfkYAA/nMqdDo1KwB8sWjTKzmD9mRCh1Q1n+g==} + vite-plus@1.3.0: + resolution: {integrity: sha512-wZGsDeYfPcrCegqgY68pJJHGTpEZdsZvIMH2/mWXkGkBD72UY3M7z24Sv3ZeOL/FrqbbtyqBOfkKO6BvoQ8wMw==} engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: - '@vitest/browser-playwright': 5.0.1 + '@vitest/browser-playwright': 5.0.5 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + playwright: '*' peerDependenciesMeta: '@vitest/browser-playwright': optional: true '@vitest/browser-webdriverio': optional: true + playwright: + optional: true vite@8.3.4: resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} @@ -12064,20 +11977,20 @@ packages: vite: optional: true - vitest@5.0.1: - resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==} + vitest@5.0.5: + resolution: {integrity: sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': 24.12.4 - '@vitest/browser-playwright': 5.0.1 - '@vitest/browser-preview': 5.0.1 + '@vitest/browser-playwright': 5.0.5 + '@vitest/browser-preview': 5.0.5 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.1 - '@vitest/coverage-v8': 5.0.1 - '@vitest/ui': 5.0.1 + '@vitest/coverage-istanbul': 5.0.5 + '@vitest/coverage-v8': 5.0.5 + '@vitest/ui': 5.0.5 happy-dom: '*' jsdom: '*' vite: ^6.4.0 || ^7.0.0 || ^8.0.0 @@ -12280,9 +12193,9 @@ packages: engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} hasBin: true workerd@1.20260918.1: @@ -12426,14 +12339,13 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - yuku-ast@0.9.5: - resolution: {integrity: sha512-Q8qW8WwQnN5Cm0ZZivdRIfv0sRLTjUq0YumXJkw8CYN1aCdICH9rk4C47/4n6kA5EqDtlj+F608twoTSV2MwdQ==} - - yuku-codegen@0.9.5: - resolution: {integrity: sha512-zGUVyDpSK4b6c9B0yyxi2P0wujn1XZTbG9dCb7d6gyAoP63EMgyLzUwPUvwxPG5jgYqhlI7w+S3oCyapqGr4PA==} + yuku-core@0.19.0: + resolution: {integrity: sha512-kYK9e1XdfirNhkipzW0wmMcSWcVF/CWnuugodfVYRCUwvoBefweL9HzB96OiKEhsx78V5WbUxbJqlizMX869Qg==} + engines: {node: '>=14.18.0'} - yuku-parser@0.9.5: - resolution: {integrity: sha512-IBnAdNVMswWbJcM7woPluOudectJzFjJ1N8jVRxP9Uq4CIv5hZdBIcdmtRbFDYF/Ph2j2gVup4YJ4N9mh0jYFA==} + yuku-parser@0.19.0: + resolution: {integrity: sha512-XrBh9ZiQK5mJVRxZBAvD6LwRyAYrK3MFtH3+onyuxFL5hFD44EC3QOmIC1FjTj6H2MMwq46/NAI+NZxLZPZgxg==} + engines: {node: '>=14.18.0'} zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} @@ -12500,7 +12412,7 @@ snapshots: dependencies: json-schema: 0.4.0 - '@alchemy.run/cloudflare-runtime@2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2)': + '@alchemy.run/cloudflare-runtime@2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2)': dependencies: '@alchemy.run/node-utils': 2.0.0-beta.80 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260918.1) @@ -12517,7 +12429,7 @@ snapshots: optionalDependencies: '@effect/platform-node': 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) rolldown: 1.2.5 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: - '@types/node' - proxy-agent @@ -13927,21 +13839,25 @@ snapshots: '@effect/tsgo-win32-arm64': 0.51.1 '@effect/tsgo-win32-x64': 0.51.1 - '@effect/vitest@4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': + '@effect/vitest@4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': dependencies: effect: 4.0.5 - vite-plus: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) transitivePeerDependencies: - '@arethetypeswrong/core' + - '@astrojs/compiler-rs' - '@edge-runtime/vm' - '@opentelemetry/api' - '@types/node' + - '@typescript/native-preview' - '@vitejs/devtools' - '@vitest/browser-playwright' - '@vitest/browser-webdriverio' - '@vitest/coverage-istanbul' - '@vitest/coverage-v8' - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' - bufferutil - esbuild - happy-dom @@ -13949,6 +13865,7 @@ snapshots: - jsdom - less - msw + - playwright - publint - sass - sass-embedded @@ -13961,6 +13878,7 @@ snapshots: - unplugin-unused - unrun - utf-8-validate + - vue-tsc - yaml '@electric-sql/pglite@0.3.15': @@ -15509,7 +15427,7 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.148.0': optional: true - '@oxc-project/runtime@0.151.0': {} + '@oxc-project/runtime@0.154.0': {} '@oxc-project/types@0.127.0': optional: true @@ -15523,11 +15441,11 @@ snapshots: '@oxc-project/types@0.148.0': optional: true - '@oxc-project/types@0.151.0': {} - '@oxc-project/types@0.153.0': optional: true + '@oxc-project/types@0.154.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true @@ -15646,141 +15564,141 @@ snapshots: '@oxc-transform-react/binding-win32-x64-msvc@0.152.0': optional: true - '@oxfmt/binding-android-arm-eabi@0.70.0': + '@oxfmt/binding-android-arm-eabi@0.73.0': optional: true - '@oxfmt/binding-android-arm64@0.70.0': + '@oxfmt/binding-android-arm64@0.73.0': optional: true - '@oxfmt/binding-darwin-arm64@0.70.0': + '@oxfmt/binding-darwin-arm64@0.73.0': optional: true - '@oxfmt/binding-darwin-x64@0.70.0': + '@oxfmt/binding-darwin-x64@0.73.0': optional: true - '@oxfmt/binding-freebsd-x64@0.70.0': + '@oxfmt/binding-freebsd-x64@0.73.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.70.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.70.0': + '@oxfmt/binding-linux-arm-musleabihf@0.73.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.70.0': + '@oxfmt/binding-linux-arm64-gnu@0.73.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.70.0': + '@oxfmt/binding-linux-arm64-musl@0.73.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.70.0': + '@oxfmt/binding-linux-ppc64-gnu@0.73.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.70.0': + '@oxfmt/binding-linux-riscv64-gnu@0.73.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.70.0': + '@oxfmt/binding-linux-riscv64-musl@0.73.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.70.0': + '@oxfmt/binding-linux-s390x-gnu@0.73.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.70.0': + '@oxfmt/binding-linux-x64-gnu@0.73.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.70.0': + '@oxfmt/binding-linux-x64-musl@0.73.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.70.0': + '@oxfmt/binding-openharmony-arm64@0.73.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.70.0': + '@oxfmt/binding-win32-arm64-msvc@0.73.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.70.0': + '@oxfmt/binding-win32-ia32-msvc@0.73.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.70.0': + '@oxfmt/binding-win32-x64-msvc@0.73.0': optional: true - '@oxlint-tsgolint/darwin-arm64@7.0.2003': + '@oxlint-tsgolint/darwin-arm64@7.0.2004': optional: true - '@oxlint-tsgolint/darwin-x64@7.0.2003': + '@oxlint-tsgolint/darwin-x64@7.0.2004': optional: true - '@oxlint-tsgolint/linux-arm64@7.0.2003': + '@oxlint-tsgolint/linux-arm64@7.0.2004': optional: true - '@oxlint-tsgolint/linux-x64@7.0.2003': + '@oxlint-tsgolint/linux-x64@7.0.2004': optional: true - '@oxlint-tsgolint/win32-arm64@7.0.2003': + '@oxlint-tsgolint/win32-arm64@7.0.2004': optional: true - '@oxlint-tsgolint/win32-x64@7.0.2003': + '@oxlint-tsgolint/win32-x64@7.0.2004': optional: true - '@oxlint/binding-android-arm-eabi@1.85.0': + '@oxlint/binding-android-arm-eabi@1.88.0': optional: true - '@oxlint/binding-android-arm64@1.85.0': + '@oxlint/binding-android-arm64@1.88.0': optional: true - '@oxlint/binding-darwin-arm64@1.85.0': + '@oxlint/binding-darwin-arm64@1.88.0': optional: true - '@oxlint/binding-darwin-x64@1.85.0': + '@oxlint/binding-darwin-x64@1.88.0': optional: true - '@oxlint/binding-freebsd-x64@1.85.0': + '@oxlint/binding-freebsd-x64@1.88.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.85.0': + '@oxlint/binding-linux-arm-gnueabihf@1.88.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.85.0': + '@oxlint/binding-linux-arm-musleabihf@1.88.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.85.0': + '@oxlint/binding-linux-arm64-gnu@1.88.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.85.0': + '@oxlint/binding-linux-arm64-musl@1.88.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.85.0': + '@oxlint/binding-linux-ppc64-gnu@1.88.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.85.0': + '@oxlint/binding-linux-riscv64-gnu@1.88.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.85.0': + '@oxlint/binding-linux-riscv64-musl@1.88.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.85.0': + '@oxlint/binding-linux-s390x-gnu@1.88.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.85.0': + '@oxlint/binding-linux-x64-gnu@1.88.0': optional: true - '@oxlint/binding-linux-x64-musl@1.85.0': + '@oxlint/binding-linux-x64-musl@1.88.0': optional: true - '@oxlint/binding-openharmony-arm64@1.85.0': + '@oxlint/binding-openharmony-arm64@1.88.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.85.0': + '@oxlint/binding-win32-arm64-msvc@1.88.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.85.0': + '@oxlint/binding-win32-ia32-msvc@1.88.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.85.0': + '@oxlint/binding-win32-x64-msvc@1.88.0': optional: true '@oxlint/plugins@1.68.0': {} - '@oxlint/plugins@1.79.0': {} + '@oxlint/plugins@1.88.0': {} '@peculiar/asn1-schema@2.8.0': dependencies: @@ -16127,7 +16045,7 @@ snapshots: '@react-navigation/routers@7.6.5': dependencies: - nanoid: 3.3.19 + nanoid: 3.3.20 '@redis/bloom@6.2.1(@redis/client@6.2.1)': dependencies: @@ -16288,7 +16206,7 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.2.5': optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13)': + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13)': dependencies: '@babel/core': 7.29.7 picomatch: 4.0.7 @@ -16296,7 +16214,7 @@ snapshots: optionalDependencies: '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) '@babel/runtime': 7.29.7 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' optional: true '@rolldown/pluginutils@1.0.0-rc.17': @@ -16523,12 +16441,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' '@tanstack/devtools-event-client@0.4.3': {} @@ -16591,7 +16509,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))': + '@tanstack/router-plugin@1.168.13(@tanstack/react-router@1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) @@ -16608,7 +16526,7 @@ snapshots: zod: 4.4.3 optionalDependencies: '@tanstack/react-router': 1.170.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: - supports-color @@ -16632,7 +16550,7 @@ snapshots: '@tanstack/virtual-file-routes@1.162.0': {} - '@testing-library/dom@10.4.1': + '@testing-library/dom@10.4.2': dependencies: '@babel/code-frame': 7.29.7 '@babel/runtime': 7.29.7 @@ -16643,9 +16561,9 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/user-event@14.6.7(@testing-library/dom@10.4.1)': + '@testing-library/user-event@14.6.7(@testing-library/dom@10.4.2)': dependencies: - '@testing-library/dom': 10.4.1 + '@testing-library/dom': 10.4.2 '@tiptap/core@3.31.3(@tiptap/pm@3.31.3)': dependencies: @@ -17128,35 +17046,35 @@ snapshots: optionalDependencies: ajv: 6.15.0 - '@vitejs/plugin-react@6.1.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0)': + '@vitejs/plugin-react@6.1.2(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)(oxc-transform-react@0.152.0)': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(rolldown@1.2.13) babel-plugin-react-compiler: 1.0.0 oxc-transform-react: 0.152.0 - '@vitest/browser-playwright@5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.1)': + '@vitest/browser-playwright@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/mocker': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser-playwright@5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1)': + '@vitest/browser-playwright@5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: - '@vitest/browser': 5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1) - '@vitest/mocker': 5.0.1(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + '@vitest/browser': 5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/mocker': 5.0.5(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) playwright: 1.60.0 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -17164,24 +17082,24 @@ snapshots: - vite optional: true - '@vitest/browser-preview@5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1)': + '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5)': dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.1) - '@vitest/browser': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser-preview@5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1)': + '@vitest/browser-preview@5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.1) - '@vitest/browser': 5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1) - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -17189,17 +17107,17 @@ snapshots: - vite optional: true - '@vitest/browser@5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1)': + '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) - '@vitest/ui': 5.0.1(vitest@5.0.1) - '@vitest/utils': 5.0.1 - magic-string: 1.4.1 + '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil @@ -17207,17 +17125,17 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1)': + '@vitest/browser@5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.1(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) - '@vitest/ui': 5.0.1(vitest@5.0.1) - '@vitest/utils': 5.0.1 - magic-string: 1.4.1 + '@vitest/mocker': 5.0.5(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + '@vitest/ui': 5.0.5(vitest@5.0.5) + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil @@ -17226,74 +17144,74 @@ snapshots: - vite optional: true - '@vitest/mocker@5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.1 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.4.1 + magic-string: 1.4.3 optionalDependencies: msw: 2.12.11(@types/node@24.12.4)(typescript@7.0.2) - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' - '@vitest/mocker@5.0.1(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@vitest/mocker@5.0.5(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.1 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 - magic-string: 1.4.1 + magic-string: 1.4.3 optionalDependencies: msw: 2.12.11(@types/node@24.12.4)(typescript@7.0.2) vite: 8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) optional: true - '@vitest/pretty-format@5.0.1': + '@vitest/pretty-format@5.0.5': dependencies: - tinyrainbow: 3.1.1 + tinyrainbow: 3.2.0 - '@vitest/snapshot@5.0.1': + '@vitest/snapshot@5.0.5': dependencies: - '@vitest/pretty-format': 5.0.1 - '@vitest/utils': 5.0.1 - magic-string: 1.4.1 + '@vitest/pretty-format': 5.0.5 + '@vitest/utils': 5.0.5 + magic-string: 1.4.3 pathe: 2.0.3 - '@vitest/spy@5.0.1': {} + '@vitest/spy@5.0.5': {} - '@vitest/ui@5.0.1(vitest@5.0.1)': + '@vitest/ui@5.0.5(vitest@5.0.5)': dependencies: - '@vitest/utils': 5.0.1 + '@vitest/utils': 5.0.5 fflate: 0.8.3 flatted: 3.4.4 pathe: 2.0.3 sirv: 3.0.2 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + tinyrainbow: 3.2.0 + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) - '@vitest/utils@5.0.1': + '@vitest/utils@5.0.5': dependencies: - '@vitest/pretty-format': 5.0.1 + '@vitest/pretty-format': 5.0.5 convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + tinyrainbow: 3.2.0 - '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)': + '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)': dependencies: - '@oxc-project/runtime': 0.151.0 - '@oxc-project/types': 0.151.0 + '@oxc-project/runtime': 0.154.0 + '@oxc-project/types': 0.154.0 lightningcss: 1.33.0 - postcss: 8.5.28 - yuku-codegen: 0.9.5 - yuku-parser: 0.9.5 + postcss: 8.5.30 + yuku-parser: 0.19.0 optionalDependencies: '@types/node': 24.12.4 - '@voidzero-dev/vite-plus-darwin-arm64': 1.0.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.0.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.0.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.0.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@volar/typescript': 2.4.28 esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 @@ -17302,24 +17220,24 @@ snapshots: unrun: 0.2.39 yaml: 2.9.0 - '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)': + '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)': dependencies: - '@oxc-project/runtime': 0.151.0 - '@oxc-project/types': 0.151.0 + '@oxc-project/runtime': 0.154.0 + '@oxc-project/types': 0.154.0 lightningcss: 1.33.0 - postcss: 8.5.28 - yuku-codegen: 0.9.5 - yuku-parser: 0.9.5 + postcss: 8.5.30 + yuku-parser: 0.19.0 optionalDependencies: '@types/node': 24.12.4 - '@voidzero-dev/vite-plus-darwin-arm64': 1.0.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.0.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.0.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.0.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@volar/typescript': 2.4.28 esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 @@ -17328,28 +17246,28 @@ snapshots: unrun: 0.2.39 yaml: 2.9.0 - '@voidzero-dev/vite-plus-darwin-arm64@1.0.0': + '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': optional: true - '@voidzero-dev/vite-plus-darwin-x64@1.0.0': + '@voidzero-dev/vite-plus-darwin-x64@1.3.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.0.0': + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-musl@1.0.0': + '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@1.0.0': + '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-musl@1.0.0': + '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.0.0': + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@1.0.0': + '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': optional: true '@volar/kit@2.4.28(typescript@6.0.3)': @@ -17408,79 +17326,43 @@ snapshots: '@xmldom/xmldom@0.9.12': {} - '@yuku-codegen/binding-android-arm64@0.9.5': - optional: true - - '@yuku-codegen/binding-darwin-arm64@0.9.5': - optional: true - - '@yuku-codegen/binding-darwin-x64@0.9.5': - optional: true - - '@yuku-codegen/binding-freebsd-x64@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-arm-gnu@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-arm-musl@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-arm64-gnu@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-arm64-musl@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-x64-gnu@0.9.5': - optional: true - - '@yuku-codegen/binding-linux-x64-musl@0.9.5': + '@yuku-core/binding-android-arm64@0.19.0': optional: true - '@yuku-codegen/binding-win32-arm64@0.9.5': + '@yuku-core/binding-darwin-arm64@0.19.0': optional: true - '@yuku-codegen/binding-win32-x64@0.9.5': + '@yuku-core/binding-darwin-x64@0.19.0': optional: true - '@yuku-parser/binding-android-arm64@0.9.5': + '@yuku-core/binding-freebsd-x64@0.19.0': optional: true - '@yuku-parser/binding-darwin-arm64@0.9.5': + '@yuku-core/binding-linux-arm-gnu@0.19.0': optional: true - '@yuku-parser/binding-darwin-x64@0.9.5': + '@yuku-core/binding-linux-arm-musl@0.19.0': optional: true - '@yuku-parser/binding-freebsd-x64@0.9.5': + '@yuku-core/binding-linux-arm64-gnu@0.19.0': optional: true - '@yuku-parser/binding-linux-arm-gnu@0.9.5': + '@yuku-core/binding-linux-arm64-musl@0.19.0': optional: true - '@yuku-parser/binding-linux-arm-musl@0.9.5': + '@yuku-core/binding-linux-x64-gnu@0.19.0': optional: true - '@yuku-parser/binding-linux-arm64-gnu@0.9.5': + '@yuku-core/binding-linux-x64-musl@0.19.0': optional: true - '@yuku-parser/binding-linux-arm64-musl@0.9.5': + '@yuku-core/binding-win32-arm64@0.19.0': optional: true - '@yuku-parser/binding-linux-x64-gnu@0.9.5': + '@yuku-core/binding-win32-x64@0.19.0': optional: true - '@yuku-parser/binding-linux-x64-musl@0.9.5': - optional: true - - '@yuku-parser/binding-win32-arm64@0.9.5': - optional: true - - '@yuku-parser/binding-win32-x64@0.9.5': - optional: true - - '@yuku-toolchain/types@0.9.5': {} + '@yuku-toolchain/types@0.19.0': {} '@yuuang/ffi-rs-android-arm64@1.3.2': optional: true @@ -17584,9 +17466,9 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alchemy@2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(c218479f13e455b978e1beb907da1b95): + alchemy@2.0.0-beta.80(patch_hash=bea1b6c0c0b23157fd5f439a1d7feff430f28ac1621ba135d8a13eeec60ccacd)(d214a55921b9b1c12454f5ddcf93cf1a): dependencies: - '@alchemy.run/cloudflare-runtime': 2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2) + '@alchemy.run/cloudflare-runtime': 2.0.0-beta.80(@distilled.cloud/cloudflare@1.0.0-rc.13(effect@4.0.5))(@effect/platform-node@4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(effect@4.0.5)(rolldown@1.2.5)(typescript@7.0.2) '@alchemy.run/floci': 2.0.0-beta.80(effect@4.0.5) '@alchemy.run/node-utils': 2.0.0-beta.80 '@alchemy.run/sigil': 0.1.0-alpha.1(@types/react@19.3.0)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6)) @@ -17625,12 +17507,12 @@ snapshots: optionalDependencies: '@effect/platform-node': 4.0.5(bufferutil@4.1.0)(effect@4.0.5)(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/sql-pg': 4.0.5(effect@4.0.5) - '@effect/vitest': 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@effect/vitest': 4.0.5(patch_hash=2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02)(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(effect@4.0.5)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) drizzle-kit: 1.0.0-rc.5-ab785fc drizzle-orm: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.0.0(effect@4.0.5))(@effect/sql-pg@4.0.5(effect@4.0.5))(@effect/sql-sqlite-bun@4.0.0-rc.112(effect@4.0.5))(@effect/sql-sqlite-do@4.0.5(effect@4.0.5))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bun-types@1.3.14)(effect@4.0.5)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7)(@react-native/metro-config@0.88.0(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(valibot@1.2.0(typescript@7.0.2))(zod@4.4.3) mongodb: 6.21.0(@aws-sdk/credential-providers@3.1062.0)(socks@2.8.9) pg: 8.23.0 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - '@types/node' @@ -17752,7 +17634,7 @@ snapshots: assertion-error@2.0.1: {} - astro@7.3.5(@types/node@24.12.4)(aws4fetch@1.0.20)(idb-keyval@6.2.1)(ioredis@5.11.0)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0): + astro@7.3.5(@types/node@24.12.4)(@volar/typescript@2.4.28)(aws4fetch@1.0.20)(idb-keyval@6.2.1)(ioredis@5.11.0)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0): dependencies: '@astrojs/compiler-rs': 0.5.1 '@astrojs/internal-helpers': 0.11.0 @@ -17802,8 +17684,8 @@ snapshots: unifont: 0.7.5 unstorage: 1.17.5(aws4fetch@1.0.20)(idb-keyval@6.2.1)(ioredis@5.11.0) verkit: 0.4.1 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)' - vitefu: 1.1.3(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)) + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)' + vitefu: 1.1.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)) xxhash-wasm: 1.1.0 yargs-parser: 22.0.0 zod: 4.6.5 @@ -17822,11 +17704,14 @@ snapshots: - '@netlify/blobs' - '@planetscale/database' - '@types/node' + - '@typescript/native-preview' - '@upstash/redis' - '@vercel/blob' - '@vercel/functions' - '@vercel/kv' - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - aws4fetch - db0 - idb-keyval @@ -17844,6 +17729,7 @@ snapshots: - unplugin-unused - unrun - uploadthing + - vue-tsc - yaml async-exit-hook@2.0.1: {} @@ -18149,7 +18035,7 @@ snapshots: ccount@2.0.1: {} - chai@6.2.2: {} + chai@6.3.0: {} chalk@2.4.2: dependencies: @@ -20221,6 +20107,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + magicast@0.5.5: dependencies: '@babel/parser': 7.29.8 @@ -21108,8 +20998,6 @@ snapshots: object-keys@1.1.1: optional: true - obug@2.2.1: {} - obug@3.0.0: {} ofetch@1.5.1: @@ -21278,112 +21166,114 @@ snapshots: '@oxc-transform-react/binding-win32-ia32-msvc': 0.152.0 '@oxc-transform-react/binding-win32-x64-msvc': 0.152.0 - oxfmt@0.70.0(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + oxfmt@0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): dependencies: - tinypool: 2.1.2 + tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.70.0 - '@oxfmt/binding-android-arm64': 0.70.0 - '@oxfmt/binding-darwin-arm64': 0.70.0 - '@oxfmt/binding-darwin-x64': 0.70.0 - '@oxfmt/binding-freebsd-x64': 0.70.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.70.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.70.0 - '@oxfmt/binding-linux-arm64-gnu': 0.70.0 - '@oxfmt/binding-linux-arm64-musl': 0.70.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.70.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.70.0 - '@oxfmt/binding-linux-riscv64-musl': 0.70.0 - '@oxfmt/binding-linux-s390x-gnu': 0.70.0 - '@oxfmt/binding-linux-x64-gnu': 0.70.0 - '@oxfmt/binding-linux-x64-musl': 0.70.0 - '@oxfmt/binding-openharmony-arm64': 0.70.0 - '@oxfmt/binding-win32-arm64-msvc': 0.70.0 - '@oxfmt/binding-win32-ia32-msvc': 0.70.0 - '@oxfmt/binding-win32-x64-msvc': 0.70.0 - vite-plus: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) - - oxfmt@0.70.0(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): - dependencies: - tinypool: 2.1.2 + '@astrojs/compiler-rs': 0.5.1 + '@oxfmt/binding-android-arm-eabi': 0.73.0 + '@oxfmt/binding-android-arm64': 0.73.0 + '@oxfmt/binding-darwin-arm64': 0.73.0 + '@oxfmt/binding-darwin-x64': 0.73.0 + '@oxfmt/binding-freebsd-x64': 0.73.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 + '@oxfmt/binding-linux-arm64-gnu': 0.73.0 + '@oxfmt/binding-linux-arm64-musl': 0.73.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-musl': 0.73.0 + '@oxfmt/binding-linux-s390x-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-musl': 0.73.0 + '@oxfmt/binding-openharmony-arm64': 0.73.0 + '@oxfmt/binding-win32-arm64-msvc': 0.73.0 + '@oxfmt/binding-win32-ia32-msvc': 0.73.0 + '@oxfmt/binding-win32-x64-msvc': 0.73.0 + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + + oxfmt@0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + dependencies: + tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.70.0 - '@oxfmt/binding-android-arm64': 0.70.0 - '@oxfmt/binding-darwin-arm64': 0.70.0 - '@oxfmt/binding-darwin-x64': 0.70.0 - '@oxfmt/binding-freebsd-x64': 0.70.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.70.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.70.0 - '@oxfmt/binding-linux-arm64-gnu': 0.70.0 - '@oxfmt/binding-linux-arm64-musl': 0.70.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.70.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.70.0 - '@oxfmt/binding-linux-riscv64-musl': 0.70.0 - '@oxfmt/binding-linux-s390x-gnu': 0.70.0 - '@oxfmt/binding-linux-x64-gnu': 0.70.0 - '@oxfmt/binding-linux-x64-musl': 0.70.0 - '@oxfmt/binding-openharmony-arm64': 0.70.0 - '@oxfmt/binding-win32-arm64-msvc': 0.70.0 - '@oxfmt/binding-win32-ia32-msvc': 0.70.0 - '@oxfmt/binding-win32-x64-msvc': 0.70.0 - vite-plus: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) - - oxlint-tsgolint@7.0.2003: + '@astrojs/compiler-rs': 0.5.1 + '@oxfmt/binding-android-arm-eabi': 0.73.0 + '@oxfmt/binding-android-arm64': 0.73.0 + '@oxfmt/binding-darwin-arm64': 0.73.0 + '@oxfmt/binding-darwin-x64': 0.73.0 + '@oxfmt/binding-freebsd-x64': 0.73.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 + '@oxfmt/binding-linux-arm64-gnu': 0.73.0 + '@oxfmt/binding-linux-arm64-musl': 0.73.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-musl': 0.73.0 + '@oxfmt/binding-linux-s390x-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-musl': 0.73.0 + '@oxfmt/binding-openharmony-arm64': 0.73.0 + '@oxfmt/binding-win32-arm64-msvc': 0.73.0 + '@oxfmt/binding-win32-ia32-msvc': 0.73.0 + '@oxfmt/binding-win32-x64-msvc': 0.73.0 + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + + oxlint-tsgolint@7.0.2004: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 7.0.2003 - '@oxlint-tsgolint/darwin-x64': 7.0.2003 - '@oxlint-tsgolint/linux-arm64': 7.0.2003 - '@oxlint-tsgolint/linux-x64': 7.0.2003 - '@oxlint-tsgolint/win32-arm64': 7.0.2003 - '@oxlint-tsgolint/win32-x64': 7.0.2003 - - oxlint@1.85.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + '@oxlint-tsgolint/darwin-arm64': 7.0.2004 + '@oxlint-tsgolint/darwin-x64': 7.0.2004 + '@oxlint-tsgolint/linux-arm64': 7.0.2004 + '@oxlint-tsgolint/linux-x64': 7.0.2004 + '@oxlint-tsgolint/win32-arm64': 7.0.2004 + '@oxlint-tsgolint/win32-x64': 7.0.2004 + + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.85.0 - '@oxlint/binding-android-arm64': 1.85.0 - '@oxlint/binding-darwin-arm64': 1.85.0 - '@oxlint/binding-darwin-x64': 1.85.0 - '@oxlint/binding-freebsd-x64': 1.85.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.85.0 - '@oxlint/binding-linux-arm-musleabihf': 1.85.0 - '@oxlint/binding-linux-arm64-gnu': 1.85.0 - '@oxlint/binding-linux-arm64-musl': 1.85.0 - '@oxlint/binding-linux-ppc64-gnu': 1.85.0 - '@oxlint/binding-linux-riscv64-gnu': 1.85.0 - '@oxlint/binding-linux-riscv64-musl': 1.85.0 - '@oxlint/binding-linux-s390x-gnu': 1.85.0 - '@oxlint/binding-linux-x64-gnu': 1.85.0 - '@oxlint/binding-linux-x64-musl': 1.85.0 - '@oxlint/binding-openharmony-arm64': 1.85.0 - '@oxlint/binding-win32-arm64-msvc': 1.85.0 - '@oxlint/binding-win32-ia32-msvc': 1.85.0 - '@oxlint/binding-win32-x64-msvc': 1.85.0 - oxlint-tsgolint: 7.0.2003 - vite-plus: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) - - oxlint@1.85.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): + '@oxlint/binding-android-arm-eabi': 1.88.0 + '@oxlint/binding-android-arm64': 1.88.0 + '@oxlint/binding-darwin-arm64': 1.88.0 + '@oxlint/binding-darwin-x64': 1.88.0 + '@oxlint/binding-freebsd-x64': 1.88.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 + '@oxlint/binding-linux-arm-musleabihf': 1.88.0 + '@oxlint/binding-linux-arm64-gnu': 1.88.0 + '@oxlint/binding-linux-arm64-musl': 1.88.0 + '@oxlint/binding-linux-ppc64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-musl': 1.88.0 + '@oxlint/binding-linux-s390x-gnu': 1.88.0 + '@oxlint/binding-linux-x64-gnu': 1.88.0 + '@oxlint/binding-linux-x64-musl': 1.88.0 + '@oxlint/binding-openharmony-arm64': 1.88.0 + '@oxlint/binding-win32-arm64-msvc': 1.88.0 + '@oxlint/binding-win32-ia32-msvc': 1.88.0 + '@oxlint/binding-win32-x64-msvc': 1.88.0 + oxlint-tsgolint: 7.0.2004 + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.85.0 - '@oxlint/binding-android-arm64': 1.85.0 - '@oxlint/binding-darwin-arm64': 1.85.0 - '@oxlint/binding-darwin-x64': 1.85.0 - '@oxlint/binding-freebsd-x64': 1.85.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.85.0 - '@oxlint/binding-linux-arm-musleabihf': 1.85.0 - '@oxlint/binding-linux-arm64-gnu': 1.85.0 - '@oxlint/binding-linux-arm64-musl': 1.85.0 - '@oxlint/binding-linux-ppc64-gnu': 1.85.0 - '@oxlint/binding-linux-riscv64-gnu': 1.85.0 - '@oxlint/binding-linux-riscv64-musl': 1.85.0 - '@oxlint/binding-linux-s390x-gnu': 1.85.0 - '@oxlint/binding-linux-x64-gnu': 1.85.0 - '@oxlint/binding-linux-x64-musl': 1.85.0 - '@oxlint/binding-openharmony-arm64': 1.85.0 - '@oxlint/binding-win32-arm64-msvc': 1.85.0 - '@oxlint/binding-win32-ia32-msvc': 1.85.0 - '@oxlint/binding-win32-x64-msvc': 1.85.0 - oxlint-tsgolint: 7.0.2003 - vite-plus: 1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + '@oxlint/binding-android-arm-eabi': 1.88.0 + '@oxlint/binding-android-arm64': 1.88.0 + '@oxlint/binding-darwin-arm64': 1.88.0 + '@oxlint/binding-darwin-x64': 1.88.0 + '@oxlint/binding-freebsd-x64': 1.88.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 + '@oxlint/binding-linux-arm-musleabihf': 1.88.0 + '@oxlint/binding-linux-arm64-gnu': 1.88.0 + '@oxlint/binding-linux-arm64-musl': 1.88.0 + '@oxlint/binding-linux-ppc64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-musl': 1.88.0 + '@oxlint/binding-linux-s390x-gnu': 1.88.0 + '@oxlint/binding-linux-x64-gnu': 1.88.0 + '@oxlint/binding-linux-x64-musl': 1.88.0 + '@oxlint/binding-openharmony-arm64': 1.88.0 + '@oxlint/binding-win32-arm64-msvc': 1.88.0 + '@oxlint/binding-win32-ia32-msvc': 1.88.0 + '@oxlint/binding-win32-x64-msvc': 1.88.0 + oxlint-tsgolint: 7.0.2004 + vite-plus: 1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) p-cancelable@2.1.1: {} @@ -21567,12 +21457,6 @@ snapshots: pngjs@7.0.0: {} - postcss@8.5.28: - dependencies: - nanoid: 3.3.19 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.30: dependencies: nanoid: 3.3.20 @@ -22511,8 +22395,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -22582,8 +22464,6 @@ snapshots: sprintf-js@1.1.3: optional: true - stackback@0.0.2: {} - stackframe@1.3.4: {} stacktrace-parser@0.1.11: @@ -22787,14 +22667,12 @@ snapshots: tiny-typed-emitter@2.1.0: {} - tinybench@6.1.4: {} + tinybench@6.2.2: {} tinyclip@1.0.3: {} tinyexec@1.2.4: {} - tinyexec@1.3.0: {} - tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -22802,9 +22680,9 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinypool@2.1.2: {} + tinypool@2.2.0: {} - tinyrainbow@3.1.1: {} + tinyrainbow@3.2.0: {} tldts-core@7.4.2: {} @@ -23114,41 +22992,46 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.151.0 - '@oxlint/plugins': 1.79.0 - '@vitest/browser': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/browser-preview': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/mocker': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) - '@vitest/pretty-format': 5.0.1 - '@vitest/snapshot': 5.0.1 - '@vitest/spy': 5.0.1 - '@vitest/utils': 5.0.1 - oxfmt: 0.70.0(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) - oxlint: 1.85.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) - oxlint-tsgolint: 7.0.2003 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.154.0 + '@oxlint/plugins': 1.88.0 + '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/pretty-format': 5.0.5 + '@vitest/snapshot': 5.0.5 + '@vitest/spy': 5.0.5 + '@vitest/utils': 5.0.5 + oxfmt: 0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2004 + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) optionalDependencies: - '@vitest/browser-playwright': 5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1) - '@voidzero-dev/vite-plus-darwin-arm64': 1.0.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.0.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.0.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.0.0 + '@vitest/browser-playwright': 5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + playwright: 1.60.0 transitivePeerDependencies: - '@arethetypeswrong/core' + - '@astrojs/compiler-rs' - '@edge-runtime/vm' - '@opentelemetry/api' - '@types/node' + - '@typescript/native-preview' - '@vitejs/devtools' - '@vitest/coverage-istanbul' - '@vitest/coverage-v8' - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' - bufferutil - esbuild - happy-dom @@ -23168,43 +23051,49 @@ snapshots: - unplugin-unused - unrun - utf-8-validate + - vue-tsc - yaml - vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): - dependencies: - '@oxc-project/types': 0.151.0 - '@oxlint/plugins': 1.79.0 - '@vitest/browser': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/browser-preview': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/mocker': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) - '@vitest/pretty-format': 5.0.1 - '@vitest/snapshot': 5.0.1 - '@vitest/spy': 5.0.1 - '@vitest/utils': 5.0.1 - oxfmt: 0.70.0(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) - oxlint: 1.85.0(oxlint-tsgolint@7.0.2003)(vite-plus@1.0.0(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) - oxlint-tsgolint: 7.0.2003 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' - vitest: 5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.154.0 + '@oxlint/plugins': 1.88.0 + '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + '@vitest/pretty-format': 5.0.5 + '@vitest/snapshot': 5.0.5 + '@vitest/spy': 5.0.5 + '@vitest/utils': 5.0.5 + oxfmt: 0.73.0(@astrojs/compiler-rs@0.5.1)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@astrojs/compiler-rs@0.5.1)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28)(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)) + oxlint-tsgolint: 7.0.2004 + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + vitest: 5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) optionalDependencies: - '@vitest/browser-playwright': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.1) - '@voidzero-dev/vite-plus-darwin-arm64': 1.0.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.0.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.0.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.0.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.0.0 + '@vitest/browser-playwright': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) + '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + playwright: 1.60.0 transitivePeerDependencies: - '@arethetypeswrong/core' + - '@astrojs/compiler-rs' - '@edge-runtime/vm' - '@opentelemetry/api' - '@types/node' + - '@typescript/native-preview' - '@vitejs/devtools' - '@vitest/coverage-istanbul' - '@vitest/coverage-v8' - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' - bufferutil - esbuild - happy-dom @@ -23224,6 +23113,7 @@ snapshots: - unplugin-unused - unrun - utf-8-validate + - vue-tsc - yaml vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0): @@ -23242,56 +23132,56 @@ snapshots: yaml: 2.9.0 optional: true - vitefu@1.1.3(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)): + vitefu@1.1.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)): optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)' + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@6.0.3)(unrun@0.2.39)(yaml@2.9.0)' - vitest@5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)): + vitest@5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.4.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 std-env: 4.3.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' - why-is-node-running: 2.3.0 + vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0)' + why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.12.4 - '@vitest/browser-playwright': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/browser-preview': 5.0.1(@voidzero-dev/vite-plus-core@1.0.0(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.1) - '@vitest/ui': 5.0.1(vitest@5.0.1) + '@vitest/browser-playwright': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@24.12.4)(@volar/typescript@2.4.28)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(typescript@7.0.2)(unrun@0.2.39)(yaml@2.9.0))(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) jsdom: 30.1.0(@noble/hashes@1.8.0) transitivePeerDependencies: - msw - vitest@5.0.1(@types/node@24.12.4)(@vitest/browser-playwright@5.0.1)(@vitest/browser-preview@5.0.1)(@vitest/ui@5.0.1(vitest@5.0.1))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + vitest@5.0.5(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5(vitest@5.0.5))(jsdom@30.1.0(@noble/hashes@1.8.0))(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.1(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) - chai: 6.2.2 + '@vitest/mocker': 5.0.5(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.4.1 - obug: 2.2.1 + magic-string: 1.4.3 + obug: 3.0.0 picomatch: 4.0.7 std-env: 4.3.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 tinyglobby: 0.2.17 vite: 8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.12.4 - '@vitest/browser-playwright': 5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1) - '@vitest/browser-preview': 5.0.1(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.1) - '@vitest/ui': 5.0.1(vitest@5.0.1) + '@vitest/browser-playwright': 5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(playwright@1.60.0)(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(bufferutil@4.1.0)(msw@2.12.11(@types/node@24.12.4)(typescript@7.0.2))(utf-8-validate@6.0.6)(vite@8.3.4(@types/node@24.12.4)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.5) + '@vitest/ui': 5.0.5(vitest@5.0.5) jsdom: 30.1.0(@noble/hashes@1.8.0) transitivePeerDependencies: - msw @@ -23478,10 +23368,7 @@ snapshots: dependencies: isexe: 4.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.1: {} workerd@1.20260918.1: optionalDependencies: @@ -23613,44 +23500,27 @@ snapshots: yoctocolors@2.1.2: {} - yuku-ast@0.9.5: - dependencies: - '@yuku-toolchain/types': 0.9.5 - - yuku-codegen@0.9.5: + yuku-core@0.19.0: dependencies: - '@yuku-toolchain/types': 0.9.5 - optionalDependencies: - '@yuku-codegen/binding-android-arm64': 0.9.5 - '@yuku-codegen/binding-darwin-arm64': 0.9.5 - '@yuku-codegen/binding-darwin-x64': 0.9.5 - '@yuku-codegen/binding-freebsd-x64': 0.9.5 - '@yuku-codegen/binding-linux-arm-gnu': 0.9.5 - '@yuku-codegen/binding-linux-arm-musl': 0.9.5 - '@yuku-codegen/binding-linux-arm64-gnu': 0.9.5 - '@yuku-codegen/binding-linux-arm64-musl': 0.9.5 - '@yuku-codegen/binding-linux-x64-gnu': 0.9.5 - '@yuku-codegen/binding-linux-x64-musl': 0.9.5 - '@yuku-codegen/binding-win32-arm64': 0.9.5 - '@yuku-codegen/binding-win32-x64': 0.9.5 - - yuku-parser@0.9.5: - dependencies: - '@yuku-toolchain/types': 0.9.5 - yuku-ast: 0.9.5 + '@yuku-toolchain/types': 0.19.0 optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.9.5 - '@yuku-parser/binding-darwin-arm64': 0.9.5 - '@yuku-parser/binding-darwin-x64': 0.9.5 - '@yuku-parser/binding-freebsd-x64': 0.9.5 - '@yuku-parser/binding-linux-arm-gnu': 0.9.5 - '@yuku-parser/binding-linux-arm-musl': 0.9.5 - '@yuku-parser/binding-linux-arm64-gnu': 0.9.5 - '@yuku-parser/binding-linux-arm64-musl': 0.9.5 - '@yuku-parser/binding-linux-x64-gnu': 0.9.5 - '@yuku-parser/binding-linux-x64-musl': 0.9.5 - '@yuku-parser/binding-win32-arm64': 0.9.5 - '@yuku-parser/binding-win32-x64': 0.9.5 + '@yuku-core/binding-android-arm64': 0.19.0 + '@yuku-core/binding-darwin-arm64': 0.19.0 + '@yuku-core/binding-darwin-x64': 0.19.0 + '@yuku-core/binding-freebsd-x64': 0.19.0 + '@yuku-core/binding-linux-arm-gnu': 0.19.0 + '@yuku-core/binding-linux-arm-musl': 0.19.0 + '@yuku-core/binding-linux-arm64-gnu': 0.19.0 + '@yuku-core/binding-linux-arm64-musl': 0.19.0 + '@yuku-core/binding-linux-x64-gnu': 0.19.0 + '@yuku-core/binding-linux-x64-musl': 0.19.0 + '@yuku-core/binding-win32-arm64': 0.19.0 + '@yuku-core/binding-win32-x64': 0.19.0 + + yuku-parser@0.19.0: + dependencies: + '@yuku-toolchain/types': 0.19.0 + yuku-core: 0.19.0 zod-to-json-schema@3.25.2(zod@4.6.5): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e1d927e6f21eb..2fc5cd50e4051 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -73,8 +73,8 @@ catalog: tldts: 7.4.2 typescript: 7.0.2 undici: 8.11.2 - vite: npm:@voidzero-dev/vite-plus-core@1.0.0 - vite-plus: 1.0.0 + vite: npm:@voidzero-dev/vite-plus-core@1.3.0 + vite-plus: 1.3.0 ws: 8.22.0 yaml: ^2.9.0 # Markdown pipeline: the server counts find matches with the same parser the diff --git a/vite.config.ts b/vite.config.ts index 4b0af4d05298f..3c66ae3a5d3ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -397,7 +397,10 @@ export default defineConfig({ "react/jsx-no-constructed-context-values": "off", "react/memo-dependencies": "error", "react/preserve-manual-memoization": "error", - "react/purity": "error", + // oxlint 1.88 treats `new Date()` as impure (oxc#26894) and also flags it inside event + // handlers (oxc#27309). The compiler we ship (oxc-transform-react) still compiles + // those files, and reactCompilerCoverage.test.ts fails on real bailouts. + "react/purity": "warn", "react/refs": "error", "react/set-state-in-effect": "error", "react/set-state-in-render": "error", From 8f66e67de35ee42bc26d081d894f8c5eee6edf17 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 00:42:07 -0700 Subject: [PATCH 003/179] test: local test runs use package configs and a clean env (#22714) Co-authored-by: Claude Opus 5.5 --- apps/desktop/vite.config.ts | 5 +- apps/mobile/vite.config.ts | 6 ++- apps/server/src/testUtils/gitConfig.setup.ts | 27 ++++++++++ apps/server/vite.config.ts | 6 ++- apps/web/vite.config.ts | 5 +- packages/client-runtime/vite.config.ts | 2 +- packages/shared/src/testing/providerEnv.ts | 22 ++++++++ vite.config.ts | 54 +++++++++++++++++--- 8 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 packages/shared/src/testing/providerEnv.ts diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 65a6e7a2892fb..c0a77e9a48b77 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -150,6 +150,9 @@ export default defineConfig({ // The Windows lane runs workspace suites concurrently; filesystem-heavy // desktop integration tests can exceed Vitest's 5 second default there. testTimeout: 15_000, - setupFiles: ["../../packages/shared/src/testing/longTempDir.ts"], + setupFiles: [ + "../../packages/shared/src/testing/longTempDir.ts", + "../../packages/shared/src/testing/providerEnv.ts", + ], }, }); diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index 8fc51ff74abb0..3f66714c68815 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -20,7 +20,10 @@ export default defineConfig({ name: "unit", include: ["**/*.test.{ts,tsx,mjs}"], exclude: ["**/node_modules/**", ...reactTests], - setupFiles: ["../../packages/shared/src/testing/longTempDir.ts"], + setupFiles: [ + "../../packages/shared/src/testing/longTempDir.ts", + "../../packages/shared/src/testing/providerEnv.ts", + ], }, }, { @@ -30,6 +33,7 @@ export default defineConfig({ include: reactTests, // Native components mock React Native and render through react-dom in these tests. environment: "jsdom", + setupFiles: ["../../packages/shared/src/testing/providerEnv.ts"], }, }, ], diff --git a/apps/server/src/testUtils/gitConfig.setup.ts b/apps/server/src/testUtils/gitConfig.setup.ts index a8139563ff3bf..19065fae6a9e7 100644 --- a/apps/server/src/testUtils/gitConfig.setup.ts +++ b/apps/server/src/testUtils/gitConfig.setup.ts @@ -1,3 +1,9 @@ +// @effect-diagnostics nodeBuiltinImport:off - runs once at test setup, outside any Effect runtime. +import * as NodeChildProcess from "node:child_process"; +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; +import * as HostProcess from "@t3tools/shared/HostProcess"; + // Git for Windows installs with core.autocrlf=true, which checks committed LF // files out as CRLF and breaks every byte-exact content assertion; a signing // key or a non-default init branch on the developer's machine breaks fixtures @@ -18,3 +24,24 @@ entries.forEach(([key, value], index) => { process.env[`GIT_CONFIG_KEY_${existing + index}`] = key; process.env[`GIT_CONFIG_VALUE_${existing + index}`] = value; }); + +// Without Homebrew git, macOS resolves `git` to /usr/bin/git, an xcrun shim that costs +// about 8 ms per spawn against 3 ms for the real binary. Git-heavy files such as +// GitVcsDriverCore.test.ts and GitManager.test.ts run twice as fast with the real one. +if (HostProcess.Platform.defaultValue() === "darwin") { + const gitOnPath = (process.env.PATH ?? "") + .split(NodePath.delimiter) + .map((dir) => NodePath.join(dir, "git")) + .find((candidate) => NodeFS.existsSync(candidate)); + if (gitOnPath === "/usr/bin/git") { + try { + const realGit = NodeChildProcess.execFileSync("xcrun", ["-f", "git"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + if (NodePath.isAbsolute(realGit)) { + process.env.PATH = `${NodePath.dirname(realGit)}${NodePath.delimiter}${process.env.PATH}`; + } + } catch {} + } +} diff --git a/apps/server/vite.config.ts b/apps/server/vite.config.ts index 5c6176b28c06d..57549bec067f2 100644 --- a/apps/server/vite.config.ts +++ b/apps/server/vite.config.ts @@ -58,8 +58,12 @@ const packExecutableTargets = packExecutableTarget ] : undefined; +// The root config's `projects` route repo-root runs to package configs, +// including this one. Inheriting them would make this config a container. +const { projects: _rootTestProjects, ...baseTestConfig } = baseConfig.test ?? {}; + export default mergeConfig( - baseConfig, + { ...baseConfig, test: baseTestConfig }, defineConfig({ run: { tasks: { diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 8158f66367df9..6b91bf21de62a 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -80,7 +80,10 @@ const unitTestProject = { exclude: ["src/**/*.browser.test.{ts,tsx}"], hookTimeout: 15_000, testTimeout: 15_000, - setupFiles: ["../../packages/shared/src/testing/longTempDir.ts"], + setupFiles: [ + "../../packages/shared/src/testing/longTempDir.ts", + "../../packages/shared/src/testing/providerEnv.ts", + ], }, } satisfies TestProjectInlineConfiguration; diff --git a/packages/client-runtime/vite.config.ts b/packages/client-runtime/vite.config.ts index 7f4ff74eca29b..dbb29b7a4ef6c 100644 --- a/packages/client-runtime/vite.config.ts +++ b/packages/client-runtime/vite.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ test: { environment: "node", include: ["src/**/*.test.ts"], - setupFiles: ["../shared/src/testing/longTempDir.ts"], + setupFiles: ["../shared/src/testing/longTempDir.ts", "../shared/src/testing/providerEnv.ts"], }, }); diff --git a/packages/shared/src/testing/providerEnv.ts b/packages/shared/src/testing/providerEnv.ts new file mode 100644 index 0000000000000..21bf59e52b500 --- /dev/null +++ b/packages/shared/src/testing/providerEnv.ts @@ -0,0 +1,22 @@ +// Agent shells carry real provider credentials (CLIProxy sets ANTHROPIC_AUTH_TOKEN and +// ANTHROPIC_BASE_URL) and the agent's own T3 state. Tests that read the ambient environment +// then pass or fail by who runs them. Tests that need one of these variables set it +// themselves. Set T3CODE_TEST_KEEP_PROVIDER_ENV=1 to keep them, for example for a live run. +const PROVIDER_ENV_PREFIXES = ["ANTHROPIC_", "CLAUDE_", "CODEX_", "OPENAI_", "T3CODE_"]; + +const KEPT_ENV = new Set([ + // CI sets these so the transfer budget integration test writes its report for the job summary. + "T3CODE_TRANSFER_BUDGET_REPORT_PATH", + "T3CODE_TRANSFER_BUDGET_RESULT_PATH", + // Opt-in: points the HtmlRender tests at a headless browser, which they skip without. + "T3CODE_TEST_HEADLESS_SHELL", +]); + +if (process.env.T3CODE_TEST_KEEP_PROVIDER_ENV !== "1") { + for (const key of Object.keys(process.env)) { + if (KEPT_ENV.has(key)) continue; + if (PROVIDER_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) { + delete process.env[key]; + } + } +} diff --git a/vite.config.ts b/vite.config.ts index 3c66ae3a5d3ca..61430801adc55 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,6 +4,30 @@ import * as NodeURL from "node:url"; const DOM_TEST_ENVIRONMENT_ALLOWLIST: Array = []; +const TEST_EXCLUDE = [ + "**/.repos/**", + "**/.t3/**", + "**/node_modules/**", + "**/dist/**", + "**/dist-electron/**", + "**/.{idea,git,cache,output,temp}/**", +]; + +// Directories whose tests belong to a package's own test config. desktop also +// owns the kde-snap-shot tests through its `include`. +const TEST_CONFIG_PACKAGES = [ + "apps/desktop", + "apps/mobile", + "apps/server", + "apps/web", + "packages/client-runtime", +]; + +// `vp test run ` from the repo root hands each file to its package's +// config. Packages that borrow this config (`--config ../../vite.config.ts`) run +// from their own directory, as CI does, so they keep one plain project. +const isRepoRootTestRun = process.cwd() === import.meta.dirname; + const RESTRICTED_IMPORT_PATHS = [ { name: "@t3tools/client-runtime", @@ -56,14 +80,7 @@ export default defineConfig({ }, test: { environment: "node", - exclude: [ - "**/.repos/**", - "**/.t3/**", - "**/node_modules/**", - "**/dist/**", - "**/dist-electron/**", - "**/.{idea,git,cache,output,temp}/**", - ], + exclude: TEST_EXCLUDE, hookTimeout: 60_000, testTimeout: 60_000, allowOnly: !process.env.CI, @@ -89,7 +106,28 @@ export default defineConfig({ NodeURL.fileURLToPath( new URL("./packages/shared/src/testing/longTempDir.ts", import.meta.url), ), + NodeURL.fileURLToPath( + new URL("./packages/shared/src/testing/providerEnv.ts", import.meta.url), + ), ], + ...(isRepoRootTestRun + ? { + projects: [ + ...TEST_CONFIG_PACKAGES, + { + extends: true, + test: { + name: "root", + exclude: [ + ...TEST_EXCLUDE, + ...TEST_CONFIG_PACKAGES.map((dir) => `${dir}/**`), + "native/kde-snap-shot/**", + ], + }, + }, + ], + } + : {}), }, staged: { "*": "vp fmt --no-error-on-unmatched-pattern", From ca049dc2053806a35d55c38d5c4e7f7e9cf13659 Mon Sep 17 00:00:00 2001 From: Yash Singh Date: Wed, 21 Oct 2026 02:45:02 -0500 Subject: [PATCH 004/179] fix(mobile): restore low-latency decoding for simulator streams (#22733) --- docs/internals/devices.md | 12 +- .../src/device/avcConfig.test.ts | 122 +++++++++++++++ .../client-runtime/src/device/avcConfig.ts | 141 ++++++++++++++++++ .../client-runtime/src/device/stream.test.ts | 30 ++++ packages/client-runtime/src/device/stream.ts | 6 +- 5 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 packages/client-runtime/src/device/avcConfig.test.ts create mode 100644 packages/client-runtime/src/device/avcConfig.ts diff --git a/docs/internals/devices.md b/docs/internals/devices.md index b0d03ab76a645..6e91307f09d48 100644 --- a/docs/internals/devices.md +++ b/docs/internals/devices.md @@ -105,10 +105,14 @@ binary WebSocket; Android multiplexes SEMU-framed H.264 and JSON gestures over one WebSocket. [`deviceStream.ts`](../../apps/web/src/components/device/deviceStream.ts) speaks both so one panel covers both platforms. -Simulators encode H.264 High 5.1. Hardware decoders on some machines and all -headless browsers reject that profile, and WebCodecs is secure-context only, so -the viewer probes `isConfigSupported` and falls back to the MJPEG endpoint on -iOS. Android has no MJPEG; there the panel reports that it cannot decode. +Simulators encode H.264 High 5.1 without frame reordering, but the hub's fallback +encoder can omit the SPS VUI that declares its reorder limits. Chromium's hardware +decoder then retains frames for seconds despite `optimizeForLatency` and an empty +`decodeQueueSize`. The iOS AVCC viewer restores the missing metadata before +configuring the decoder so hardware decoding can remain enabled. WebCodecs is +secure-context only, so the viewer probes `isConfigSupported` and falls back to +MJPEG on iOS when decoding is unavailable. Android has no MJPEG; there the panel +reports that it cannot decode. serve-sim can also be locked to WebRTC, as on EAS Simulator, and then refuses AVCC with a 409 naming the transport. The viewer still asks for AVCC first, so diff --git a/packages/client-runtime/src/device/avcConfig.test.ts b/packages/client-runtime/src/device/avcConfig.test.ts new file mode 100644 index 0000000000000..4b2ff9906f3f3 --- /dev/null +++ b/packages/client-runtime/src/device/avcConfig.test.ts @@ -0,0 +1,122 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { simulatorAvcConfig } from "./avcConfig.ts"; + +// Captured from the hub before and after its encoder falls back from low-latency rate control. +const lowLatency = Uint8Array.from([ + 1, 100, 0, 51, 255, 225, 0, 22, 39, 100, 0, 51, 172, 19, 20, 60, 4, 192, 20, 158, 106, 154, 129, + 1, 1, 3, 194, 1, 8, 248, 1, 0, 4, 40, 238, 60, 176, +]); +const fallback = Uint8Array.from([ + 1, 100, 0, 51, 255, 225, 0, 12, 39, 100, 0, 51, 172, 86, 128, 76, 1, 73, 230, 164, 1, 0, 4, 40, + 238, 60, 176, +]); +const repaired = Uint8Array.from([ + 1, 100, 0, 51, 255, 225, 0, 17, 39, 100, 0, 51, 172, 86, 128, 76, 1, 73, 230, 168, 6, 208, 68, 35, + 80, 1, 0, 4, 40, 238, 60, 176, +]); + +describe("simulator AVC reorder metadata", () => { + it("adds zero reordering and one reference frame to the captured fallback SPS", () => { + expect(simulatorAvcConfig(fallback)).toEqual(repaired); + expect(simulatorAvcConfig(repaired)).toBe(repaired); + }); + + it("preserves existing VUI and leaves the original codec description untouched", () => { + const original = fallback.slice(); + simulatorAvcConfig(fallback); + expect(fallback).toEqual(original); + expect(simulatorAvcConfig(lowLatency)).toBe(lowLatency); + }); + + it("repairs multiple SPS entries while preserving PPS and profile extensions", () => { + const extensions = [0xfd, 0xf8, 0xf8, 0]; + const description = Uint8Array.from([ + ...fallback.subarray(0, 5), + 0xe2, + ...fallback.subarray(6, 20), + ...lowLatency.subarray(6, 30), + ...fallback.subarray(20), + ...extensions, + ]); + expect(simulatorAvcConfig(description)).toEqual( + Uint8Array.from([ + ...description.subarray(0, 6), + ...repaired.subarray(6, 25), + ...lowLatency.subarray(6, 30), + ...fallback.subarray(20), + ...extensions, + ]), + ); + }); + + it("passes through every truncated prefix of the captured configurations", () => { + for (const config of [fallback, lowLatency]) { + for (let length = 0; length < config.length; length++) { + const truncated = config.subarray(0, length); + expect(simulatorAvcConfig(truncated)).toBe(truncated); + } + } + }); + + it("does not modify unsupported profiles, scaling lists, or invalid parameter-set lengths", () => { + const unsupportedProfile = fallback.slice(); + unsupportedProfile[9] = 110; + const scalingLists = fallback.slice(); + scalingLists[12] = scalingLists[12]! | 1; + const invalidSpsLength = fallback.slice(); + invalidSpsLength[6] = 0xff; + const invalidPpsLength = fallback.slice(); + invalidPpsLength[21] = 0xff; + for (const config of [unsupportedProfile, scalingLists, invalidSpsLength, invalidPpsLength]) { + expect(simulatorAvcConfig(config)).toBe(config); + } + }); + + it("requires valid SPS trailing bits before appending VUI metadata", () => { + const invalidPadding = fallback.slice(); + invalidPadding[19] = 0; + expect(simulatorAvcConfig(invalidPadding)).toBe(invalidPadding); + }); + + it("passes through invalid NAL headers and unterminated Exp-Golomb values", () => { + const forbiddenBit = fallback.slice(); + forbiddenBit[8] = forbiddenBit[8]! | 0x80; + expect(simulatorAvcConfig(forbiddenBit)).toBe(forbiddenBit); + const unterminated = fallback.slice(); + unterminated.fill(0, 12, 20); + expect(simulatorAvcConfig(unterminated)).toBe(unterminated); + const overlong = Uint8Array.from([ + ...fallback.subarray(0, 6), + 0, + 13, + 39, + 100, + 0, + 51, + 0, + 0, + 3, + 0, + 0, + 3, + 0, + 0, + 0, + ...fallback.subarray(20), + ]); + expect(simulatorAvcConfig(overlong)).toBe(overlong); + }); + + it("bounds parsing of oversized configurations and SPS data", () => { + const oversizedConfig = new Uint8Array(64 * 1024 + 1); + oversizedConfig.set(fallback); + expect(simulatorAvcConfig(oversizedConfig)).toBe(oversizedConfig); + const oversizedSps = new Uint8Array(6 + 2 + 4097 + 7); + oversizedSps.set(fallback.subarray(0, 6)); + oversizedSps.set([0x10, 1], 6); + oversizedSps.set(fallback.subarray(8, 20), 8); + oversizedSps.set(fallback.subarray(20), 8 + 4097); + expect(simulatorAvcConfig(oversizedSps)).toBe(oversizedSps); + }); +}); diff --git a/packages/client-runtime/src/device/avcConfig.ts b/packages/client-runtime/src/device/avcConfig.ts new file mode 100644 index 0000000000000..1c600635799c2 --- /dev/null +++ b/packages/client-runtime/src/device/avcConfig.ts @@ -0,0 +1,141 @@ +const MAX_CONFIG_BYTES = 64 * 1024; +const MAX_SPS_BYTES = 4096; + +const unsignedGolomb = (value: number) => { + const bits = (value + 1).toString(2); + return "0".repeat(bits.length - 1) + bits; +}; + +function withReorderLimits(sps: Uint8Array): Uint8Array { + if (sps.length < 4 || sps.length > MAX_SPS_BYTES || (sps[0]! & 0x9f) !== 7) return sps; + const rbsp: number[] = []; + let zeros = 0; + for (let index = 1; index < sps.length; index++) { + const byte = sps[index]!; + if (zeros >= 2 && byte === 3) { + if (index + 1 >= sps.length || sps[index + 1]! > 3) return sps; + zeros = 0; + continue; + } + rbsp.push(byte); + zeros = byte === 0 ? zeros + 1 : 0; + } + const bits = rbsp.map((byte) => byte.toString(2).padStart(8, "0")).join(""); + let position = 0; + const read = (count: number) => { + if (position + count > bits.length) throw new Error("Truncated H.264 SPS"); + const value = Number.parseInt(bits.slice(position, position + count), 2); + position += count; + return value; + }; + const readUnsignedGolomb = () => { + let leadingZeros = 0; + while (read(1) === 0) { + if (++leadingZeros > 31) throw new Error("Invalid H.264 Exp-Golomb value"); + } + return 2 ** leadingZeros - 1 + (leadingZeros === 0 ? 0 : read(leadingZeros)); + }; + + try { + // Only handle the hub's 8-bit 4:2:0 High profile without scaling lists. + // Unknown encoder configurations pass through rather than guessing their layout. + if (read(8) !== 100) return sps; + read(16); + if (readUnsignedGolomb() > 31 || readUnsignedGolomb() !== 1) return sps; + if (readUnsignedGolomb() !== 0 || readUnsignedGolomb() !== 0) return sps; + read(1); + if (read(1) !== 0) return sps; + if (readUnsignedGolomb() > 12) return sps; + const pictureOrderCountType = readUnsignedGolomb(); + if (pictureOrderCountType === 0) { + if (readUnsignedGolomb() > 12) return sps; + } else if (pictureOrderCountType === 1) { + read(1); + readUnsignedGolomb(); + readUnsignedGolomb(); + const cycleLength = readUnsignedGolomb(); + if (cycleLength > 255) return sps; + for (let index = 0; index < cycleLength; index++) readUnsignedGolomb(); + } else if (pictureOrderCountType !== 2) return sps; + const maxReferenceFrames = readUnsignedGolomb(); + if (maxReferenceFrames > 16) return sps; + read(1); + if (readUnsignedGolomb() > 65535 || readUnsignedGolomb() > 65535) return sps; + if (read(1) === 0) read(1); + read(1); + if (read(1) !== 0) { + for (let index = 0; index < 4; index++) readUnsignedGolomb(); + } + const vuiPosition = position; + if (read(1) !== 0) return sps; + // With no VUI, only rbsp_stop_one_bit and byte padding may follow. + if (!/^10{0,7}$/.test(bits.slice(position))) return sps; + + // serve-sim disables frame reordering. Its fallback encoder omits the VUI, + // causing Chromium to infer a large reorder queue despite optimizeForLatency. + // Preserve the SPS and add VUI defaults with max_num_reorder_frames = 0. + // The flag bits enable VUI, omit its eight optional sections, and enable + // bitstream restrictions and motion_vectors_over_pic_boundaries_flag. + const restricted = + bits.slice(0, vuiPosition) + + "10000000011" + + [2, 1, 16, 16, 0, maxReferenceFrames].map(unsignedGolomb).join("") + + "1"; + const padded = restricted.padEnd(Math.ceil(restricted.length / 8) * 8, "0"); + const output = [sps[0]!]; + zeros = 0; + for (let index = 0; index < padded.length; index += 8) { + const byte = Number.parseInt(padded.slice(index, index + 8), 2); + if (zeros >= 2 && byte <= 3) { + output.push(3); + zeros = 0; + } + output.push(byte); + zeros = byte === 0 ? zeros + 1 : 0; + } + return Uint8Array.from(output); + } catch { + return sps; + } +} + +/** Repair missing reorder metadata only for serve-sim's non-reordering AVCC feed. */ +export function simulatorAvcConfig(description: Uint8Array): Uint8Array { + if (description.length < 7 || description.length > MAX_CONFIG_BYTES || description[0] !== 1) + return description; + const parameterSets = description[5]! & 0x1f; + if (parameterSets === 0) return description; + const parts: Uint8Array[] = [description.subarray(0, 6)]; + let position = 6; + let changed = false; + for (let index = 0; index < parameterSets; index++) { + if (position + 2 > description.length) return description; + const length = description[position]! * 256 + description[position + 1]!; + position += 2; + if (length === 0 || position + length > description.length) return description; + const sps = description.subarray(position, position + length); + const repaired = withReorderLimits(sps); + changed ||= repaired !== sps; + parts.push(Uint8Array.of(repaired.length >>> 8, repaired.length & 0xff), repaired); + position += length; + } + const suffix = position; + if (position >= description.length) return description; + const pictureParameterSets = description[position++]!; + for (let index = 0; index < pictureParameterSets; index++) { + if (position + 2 > description.length) return description; + const length = description[position]! * 256 + description[position + 1]!; + position += 2; + if (length === 0 || position + length > description.length) return description; + position += length; + } + if (!changed) return description; + parts.push(description.subarray(suffix)); + const output = new Uint8Array(parts.reduce((length, part) => length + part.length, 0)); + position = 0; + for (const part of parts) { + output.set(part, position); + position += part.length; + } + return output; +} diff --git a/packages/client-runtime/src/device/stream.test.ts b/packages/client-runtime/src/device/stream.test.ts index 5ffdddb26f2ee..7da96cb3d75b7 100644 --- a/packages/client-runtime/src/device/stream.test.ts +++ b/packages/client-runtime/src/device/stream.test.ts @@ -323,6 +323,36 @@ describe("shared device stream readiness and recovery", () => { vi.unstubAllGlobals(); }); + it("repairs the hub's fallback configuration without disabling hardware decoding", async () => { + const { client, videoBodies, Decoder } = recoveryFixture("ios", false); + try { + client.start(); + await vi.advanceTimersByTimeAsync(0); + videoBodies[0]!.enqueue( + Uint8Array.from( + envelope( + 1, + [ + 1, 100, 0, 51, 255, 225, 0, 12, 39, 100, 0, 51, 172, 86, 128, 76, 1, 73, 230, 164, 1, + 0, 4, 40, 238, 60, 176, + ], + ), + ), + ); + await vi.advanceTimersByTimeAsync(0); + expect(Decoder.isConfigSupported).toHaveBeenCalledExactlyOnceWith({ + codec: "avc1.640033", + description: Uint8Array.from([ + 1, 100, 0, 51, 255, 225, 0, 17, 39, 100, 0, 51, 172, 86, 128, 76, 1, 73, 230, 168, 6, 208, + 68, 35, 80, 1, 0, 4, 40, 238, 60, 176, + ]), + optimizeForLatency: true, + }); + } finally { + client.stop(); + } + }); + it("does not repaint a delayed JPEG seed over newer decoded video", async () => { const { client, videoBodies, drawImage, decodedFrame } = recoveryFixture("ios", false); let resolveBitmap!: (bitmap: ImageBitmap) => void; diff --git a/packages/client-runtime/src/device/stream.ts b/packages/client-runtime/src/device/stream.ts index 9318c50fe6918..04388e3429def 100644 --- a/packages/client-runtime/src/device/stream.ts +++ b/packages/client-runtime/src/device/stream.ts @@ -10,6 +10,7 @@ import { import * as Schema from "effect/Schema"; import * as Option from "effect/Option"; import { createCanvasFrameSink, type DeviceFrameSink } from "./frame.ts"; +import { simulatorAvcConfig } from "./avcConfig.ts"; import { type DeviceHubAccess, withDeviceHubQuery } from "./hubAccess.ts"; import type { DevicePlatform } from "@t3tools/contracts"; @@ -759,7 +760,10 @@ export function createDeviceStreamClient( case "description": { awaitingKeyframe = true; const configured = await configureDecoder( - { codec: avcCodecString(chunk.payload), description: chunk.payload }, + { + codec: avcCodecString(chunk.payload), + description: simulatorAvcConfig(chunk.payload), + }, isCurrent, ); if (!isCurrent()) return; From ea1c49419e742c688cc4951d63545046a06ac8c0 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 00:49:46 -0700 Subject: [PATCH 005/179] fix(marketing): typecheck cache no longer misses when the shell env changes (#22735) Co-authored-by: Claude Opus 5.5 --- apps/marketing/astro.config.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/marketing/astro.config.mjs b/apps/marketing/astro.config.mjs index 5ba3da4fba109..15a8a8f916333 100644 --- a/apps/marketing/astro.config.mjs +++ b/apps/marketing/astro.config.mjs @@ -1,5 +1,11 @@ import { defineConfig } from "astro/config"; +// Astro loads env with an empty prefix, and under `vp run` Vite+'s loadEnv then reports every +// variable to the task cache. Any env difference (TERM, VP_RAW_SUBCOMMAND, an agent's session +// vars) would rerun `astro check`. Without the runner client, loadEnv reads process.env directly; +// file tracking does not use this client. +delete process.env.VP_RUN_NODE_CLIENT_PATH; + export default defineConfig({ site: "https://t3.codes", server: { From 618761a2d6d615b2c0a07e5de4d82384a583f717 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 00:50:22 -0700 Subject: [PATCH 006/179] build(setup): leaner installs on Mac and Linux, cross-device store warning, setup-done marker (#22718) Co-authored-by: Claude Opus 5.5 --- .pnpmfile.cjs | 21 ++++++++++ AGENTS.md | 1 + pnpm-lock.yaml | 2 + pnpm-workspace.yaml | 5 --- scripts/setup-worktree.ts | 87 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 .pnpmfile.cjs diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs new file mode 100644 index 0000000000000..ff75832369d8d --- /dev/null +++ b/.pnpmfile.cjs @@ -0,0 +1,21 @@ +// Windows WSL dev mode runs the server under Linux from the same node_modules +// as the Windows client, so Windows installs also need Linux x64 glibc native +// modules. Other platforms install only their own binaries; setting this +// everywhere added ~2.2 GB of foreign binaries to every macOS install. +// Release scripts stage their own installs with explicit architectures. +module.exports = { + hooks: { + updateConfig(config) { + // oxlint-disable-next-line t3code/no-global-process-runtime -- pnpm loads this file before any Effect runtime exists. + if (process.platform !== "win32") return config; + return { + ...config, + supportedArchitectures: { + os: ["current", "linux"], + cpu: ["current", "x64"], + libc: ["current", "glibc"], + }, + }; + }, + }, +}; diff --git a/AGENTS.md b/AGENTS.md index e87e5bfb1d9f6..034e505b06bad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,6 +78,7 @@ The most common defect in this repo is a change that works on the path you teste ## Dev servers - `vp i` installs. Worktrees get this from the t3.json setup script; if module resolution looks broken, it probably did not run. +- Setup runs in the background. While `.t3/setup-running` names a live PID, wait for it and do not run `vp i`. It ends with `.t3/setup-done` or `.t3/setup-failed` (with the error tail). With no marker at all, setup never ran here, so run `vp i` yourself. - `vp run dev` starts server and web. In a worktree, state defaults to that worktree's gitignored `.t3`, which deliberately outranks an ambient `T3CODE_HOME` so you cannot land on shared state by accident. An explicit `--home-dir` still wins. - Ports derive from the worktree path and are stable across restarts, but read the real ones from the `[dev-runner]` line since occupied ports shift. - Sharing over the tailnet is three steps: run `vp run dev --share` in the background, wait for the `pairingUrl:` line in its output, then give that full URL to an unpaired browser. Do not wire up `tailscale serve` by hand, open the URL yourself, or consume the user's pairing link. A browser with the reusable dev cookie can use the bare origin. If a normal one-time token was consumed, mint a fresh one with `node apps/server/src/bin.ts pair`. It carries standard scopes, while the startup URL carries admin scopes needed for Connections settings. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c03fc7d0e3a4..34458e07f726e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,6 +166,8 @@ overrides: packageExtensionsChecksum: sha256-EowbsmlBFaoO0Ypufw901RnvdyjKG5dcdk2FRbc7Dr0= +pnpmfileChecksum: sha256-lS/yS+IvX/sj4KLdjVeRsq/hRipJMILKWjIbzIC+czo= + patchedDependencies: '@clerk/expo-native-components@0.2.0': 7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4 '@effect/vitest@4.0.5': 2731c9c3761c595f25ddc625a7d61ca376b389d5b3049ecb837c8940f21cfa02 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2fc5cd50e4051..715492185e13e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -373,8 +373,3 @@ peerDependencyRules: # pnpm keeps optional deps inside the virtual store, so hoist it to the root. publicHoistPattern: - "@cursor/sdk-*" - -supportedArchitectures: - cpu: [current, x64] - libc: [current, glibc] - os: [current, linux] diff --git a/scripts/setup-worktree.ts b/scripts/setup-worktree.ts index cee8b567aa978..6fabf341682c3 100644 --- a/scripts/setup-worktree.ts +++ b/scripts/setup-worktree.ts @@ -5,23 +5,102 @@ import * as NodePath from "node:path"; const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")]; +const worktree = NodePath.dirname(import.meta.dirname); + +// T3 runs setup in the background, so agents can start before it ends. +// `setup-running` holds this PID until setup ends with `setup-done` or +// `setup-failed`, so agents wait instead of racing it with their own `vp i`. +const markerDir = NodePath.join(worktree, ".t3"); +const runningMarker = NodePath.join(markerDir, "setup-running"); +const doneMarker = NodePath.join(markerDir, "setup-done"); +const failedMarker = NodePath.join(markerDir, "setup-failed"); +NodeFS.rmSync(doneMarker, { force: true }); +NodeFS.rmSync(failedMarker, { force: true }); +NodeFS.mkdirSync(markerDir, { recursive: true }); +NodeFS.writeFileSync(runningMarker, `${process.pid}\n`); +let failureDetail = ""; +process.on("uncaughtExceptionMonitor", (error) => { + failureDetail = error.stack ?? String(error); +}); +process.on("exit", (code) => { + if (code === 0) NodeFS.writeFileSync(doneMarker, ""); + else NodeFS.writeFileSync(failedMarker, `Setup exited with code ${code}.\n${failureDetail}\n`); + NodeFS.rmSync(runningMarker, { force: true }); +}); +// A cancelled setup must still leave a marker, or agents would wait forever. +// During install, the signal goes on to the installer and setup exits when the +// install output closes. Any child the shell leaves behind holds that output +// open, so no marker appears while `vp i` can still write node_modules. +let cancelSignal: NodeJS.Signals | undefined; +let activeInstall: NodeChildProcess.ChildProcess | undefined; +for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) { + process.on(signal, () => { + cancelSignal = signal; + if (activeInstall) activeInstall.kill(signal); + else process.exit(1); + }); +} + const projectRoot = process.env.T3CODE_PROJECT_ROOT; if (!projectRoot) { throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action."); } -const worktree = NodePath.dirname(import.meta.dirname); // `shell` resolves `vp` through PATH, including Windows command shims. // Setup has no one answering install prompts. Pass pnpm's confirmation flag // through vp and leave stdin closed so other prompts fail instead of hanging. // Install exactly the locked dependencies so setup never rewrites the lockfile. // A stale lockfile must fail setup rather than silently resolving a new one. -const install = NodeChildProcess.spawnSync("vp i --frozen-lockfile -- --yes", { +// Output is piped, not inherited, so a failure can keep its tail in the marker. +const install = NodeChildProcess.spawn("vp i --frozen-lockfile -- --yes", { cwd: worktree, shell: true, - stdio: ["ignore", "inherit", "inherit"], + stdio: ["ignore", "pipe", "pipe"], }); -if (install.status !== 0) process.exit(install.status ?? 1); +activeInstall = install; +let installOutput = ""; +for (const [source, target] of [ + [install.stdout, process.stdout], + [install.stderr, process.stderr], +] as const) { + source.setEncoding("utf8"); + source.on("data", (chunk: string) => { + target.write(chunk); + installOutput = (installOutput + chunk).slice(-8_000); + }); +} +const installStatus = await new Promise((resolve) => install.on("close", resolve)); +activeInstall = undefined; +if (installStatus !== 0 || cancelSignal) { + failureDetail = cancelSignal + ? `Cancelled by ${cancelSignal}.` + : installOutput.trimEnd().split("\n").slice(-30).join("\n"); + process.exit(installStatus || 1); +} + +// pnpm cannot clone or hardlink across filesystems, so a store on another +// device silently turns every worktree install into a full copy. +const storeDir: unknown = JSON.parse( + NodeFS.readFileSync(NodePath.join(worktree, "node_modules", ".modules.yaml"), "utf8"), +).storeDir; +if ( + typeof storeDir === "string" && + NodeFS.statSync(storeDir).dev !== NodeFS.statSync(NodePath.join(worktree, "node_modules")).dev +) { + process.stderr.write( + [ + "", + "WARNING: the pnpm store is on a different filesystem than this worktree.", + ` store: ${storeDir}`, + ` worktree: ${worktree}`, + "pnpm cannot hardlink or clone across filesystems, so it copied every package into node_modules.", + "Fix: put the store on the worktree's filesystem, for example:", + ` pnpm config set store-dir ${JSON.stringify(NodePath.join(NodePath.dirname(worktree), ".pnpm-store"))} --global`, + "", + "", + ].join("\n"), + ); +} // Env files live as real files in the main checkout; worktrees only get // symlinks to them. Only a symlink is ever replaced, so a real env file is From f4a3843a1c8ce2b65b535c3bfc610ea408310d1e Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 01:07:46 -0700 Subject: [PATCH 007/179] build: upgrade pnpm to 12 for 4x faster installs (#22738) Co-authored-by: Claude Opus 5.5 --- package.json | 2 +- pnpm-lock.yaml | 158 ++++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 12 +++- 3 files changed, 170 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 53580ef428a2f..363a9261163c5 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "engines": { "node": "^24.13.1" }, - "packageManager": "pnpm@11.10.0" + "packageManager": "pnpm@12.14.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34458e07f726e..ad93d5d4a4259 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3 +1,161 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.14.0 + version: 12.14.0 + +packages: + + '@pnpm/exe.android-arm64@12.14.0': + resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.14.0': + resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.14.0': + resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.14.0': + resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.14.0': + resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.14.0': + resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.14.0': + resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.14.0': + resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.14.0': + resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.14.0': + resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.14.0': + resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.14.0': + resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.14.0': + resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.14.0': + resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==} + cpu: [x64] + os: [win32] + + pnpm@12.14.0: + resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.14.0': + optional: true + + '@pnpm/exe.android-x64@12.14.0': + optional: true + + '@pnpm/exe.darwin-arm64@12.14.0': + optional: true + + '@pnpm/exe.darwin-x64@12.14.0': + optional: true + + '@pnpm/exe.freebsd-x64@12.14.0': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.14.0': + optional: true + + '@pnpm/exe.linux-arm64@12.14.0': + optional: true + + '@pnpm/exe.linux-ppc64@12.14.0': + optional: true + + '@pnpm/exe.linux-riscv64@12.14.0': + optional: true + + '@pnpm/exe.linux-s390x@12.14.0': + optional: true + + '@pnpm/exe.linux-x64-musl@12.14.0': + optional: true + + '@pnpm/exe.linux-x64@12.14.0': + optional: true + + '@pnpm/exe.win32-arm64@12.14.0': + optional: true + + '@pnpm/exe.win32-x64@12.14.0': + optional: true + + pnpm@12.14.0: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.14.0 + '@pnpm/exe.android-x64': 12.14.0 + '@pnpm/exe.darwin-arm64': 12.14.0 + '@pnpm/exe.darwin-x64': 12.14.0 + '@pnpm/exe.freebsd-x64': 12.14.0 + '@pnpm/exe.linux-arm64': 12.14.0 + '@pnpm/exe.linux-arm64-musl': 12.14.0 + '@pnpm/exe.linux-ppc64': 12.14.0 + '@pnpm/exe.linux-riscv64': 12.14.0 + '@pnpm/exe.linux-s390x': 12.14.0 + '@pnpm/exe.linux-x64': 12.14.0 + '@pnpm/exe.linux-x64-musl': 12.14.0 + '@pnpm/exe.win32-arm64': 12.14.0 + '@pnpm/exe.win32-x64': 12.14.0 + +--- lockfileVersion: '9.0' settings: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 715492185e13e..d87dd95cd0600 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,7 +5,7 @@ packages: - packages/* - scripts -# Successor to onlyBuiltDependencies/ignoredBuiltDependencies (pnpm 11). +# Successor to onlyBuiltDependencies/ignoredBuiltDependencies (pnpm 11+). # true = allowed to run build scripts; false mirrors the pnpm 10 behavior # where anything outside onlyBuiltDependencies was silently not built. allowBuilds: @@ -368,6 +368,16 @@ peerDependencyRules: allowedVersions: vite: "*" +# pnpm 12 links agent skills that dependencies ship into .claude/skills once +# approved. We curate our own skills, so deny these to keep installs quiet. +permissions: + "@tanstack/router-plugin": + skills: false + drizzle-kit: + skills: false + expo-sqlite: + skills: false + # @cursor/sdk finds its platform package (sandbox helper, rg, tree-sitter) by # walking up from the entry script for node_modules/@cursor/sdk--. # pnpm keeps optional deps inside the virtual store, so hoist it to the root. From 76e4aac3e9b90ae2395bb62fa540e90b43bd70a6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:48 -0700 Subject: [PATCH 008/179] refactor(web): the timeline minimap lives in its own file (#22662) Co-authored-by: Claude Opus 5.5 --- .../src/components/chat/MessagesTimeline.tsx | 335 +----------------- .../src/components/chat/TimelineMinimap.tsx | 335 ++++++++++++++++++ 2 files changed, 337 insertions(+), 333 deletions(-) create mode 100644 apps/web/src/components/chat/TimelineMinimap.tsx diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 1e591b5683638..bb7f613602f56 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -16,11 +16,7 @@ import { getQuestionTextPreview, hasQuestionAnswer, } from "@t3tools/client-runtime/work-log/user-input"; -import { - deriveTimelineMinimapItems, - resolveTimelineMinimapPreview, - type TimelineMinimapItem, -} from "./timelineMinimapItems"; +import { deriveTimelineMinimapItems } from "./timelineMinimapItems"; import { COMPOSER_CONTEXT_KINDS, ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE, @@ -127,7 +123,6 @@ import { CheckIcon, ChevronDownIcon, ChevronRightIcon, - ChevronUpIcon, CircleAlertIcon, DownloadIcon, EyeIcon, @@ -210,12 +205,7 @@ import { resolveTimelineMinimapCurrentIndex, isTimelineMinimapRowVisible, resolveTimelineMinimapVisibleRange, - resolveTimelineMinimapHeightStyle, resolveTimelineMinimapHitStripWidth, - resolveTimelineMinimapIndexFromPointer, - resolveTimelineMinimapInteractiveWidth, - resolveTimelineMinimapNavigationInteractive, - resolveTimelineMinimapTopPercent, resolveWorkGroupScrollIndex, shouldCollapseUserMessage, shouldFollowWorkGroupAppend, @@ -230,7 +220,6 @@ import { worktreeSetupAgentStarted, type StableMessagesTimelineRowsState, type MessagesTimelineRow, - TIMELINE_MINIMAP_MIN_ITEMS, type TimelineLatestRun, type WorkGroupScrollAnchor, } from "./MessagesTimeline.logic"; @@ -309,6 +298,7 @@ import { formatReviewCommentFence, type ReviewCommentContext, } from "../../reviewCommentContext"; +import { TimelineMinimap } from "./TimelineMinimap"; interface TimelineRowSharedState { citationRequest: AssistantCitationTarget | null; @@ -1630,327 +1620,6 @@ function getItemType(item: MessagesTimelineRow) { return `message:${item.message.role}:${lengthBucket}`; } -function timelineMinimapEventTargetsPreview(target: EventTarget): boolean { - return target instanceof Element && target.closest("[data-minimap-preview]") !== null; -} - -// A release outside the window can skip `mouseup`, so the next button-free -// `mousemove` also counts. -function onMouseButtonsReleased(onRelease: () => void): () => void { - const check = (event: globalThis.MouseEvent) => { - if (event.buttons !== 0) return; - stop(); - onRelease(); - }; - const stop = () => { - window.removeEventListener("mouseup", check); - window.removeEventListener("mousemove", check); - }; - window.addEventListener("mouseup", check); - window.addEventListener("mousemove", check); - return stop; -} - -function TimelineMinimap({ - hasPersistentGutter, - hitStripWidth, - currentIndex, - items, - stripMap, - onWheel, - onSelect, -}: { - hasPersistentGutter: boolean; - hitStripWidth: number; - currentIndex: number | null; - items: ReadonlyArray; - stripMap: Map; - onWheel: (event: WheelEvent) => void; - onSelect: (item: TimelineMinimapItem) => void; -}) { - const [activeIndex, setActiveIndex] = useState(null); - const wheelTargetRef = (node: HTMLDivElement | null) => { - if (!node) return; - node.addEventListener("wheel", onWheel, { passive: false }); - return () => node.removeEventListener("wheel", onWheel); - }; - - const pressedOnStripRef = useRef(false); - const [passThrough, setPassThrough] = useState(false); - - useEffect(() => { - if (!passThrough) return; - return onMouseButtonsReleased(() => setPassThrough(false)); - }, [passThrough]); - - const resolvedActiveIndex = - activeIndex !== null && activeIndex < items.length ? activeIndex : null; - const activeItem = resolveTimelineMinimapPreview( - resolvedActiveIndex === null ? null : (items[resolvedActiveIndex] ?? null), - ); - const navigationInteractive = - resolveTimelineMinimapNavigationInteractive(hitStripWidth) && !passThrough; - const activeTopPercent = - resolvedActiveIndex === null - ? 0 - : resolveTimelineMinimapTopPercent(resolvedActiveIndex, items.length); - const activeTooltipTranslate = - resolvedActiveIndex === null - ? "-50%" - : resolvedActiveIndex === 0 - ? "0%" - : resolvedActiveIndex === items.length - 1 - ? "-100%" - : "-50%"; - const resolvedCurrentIndex = - currentIndex !== null && currentIndex >= 0 && currentIndex < items.length ? currentIndex : null; - const previousItem = - resolvedCurrentIndex === null ? null : (items[resolvedCurrentIndex - 1] ?? null); - const nextItem = resolvedCurrentIndex === null ? null : (items[resolvedCurrentIndex + 1] ?? null); - - const resolveActiveIndexFromPointer = (event: MouseEvent) => { - const rect = event.currentTarget.getBoundingClientRect(); - return resolveTimelineMinimapIndexFromPointer({ - itemCount: items.length, - railTop: rect.top, - railHeight: rect.height, - pointerY: event.clientY, - }); - }; - - const updateActiveIndexFromPointer = (event: MouseEvent) => { - const nextIndex = resolveActiveIndexFromPointer(event); - setActiveIndex(nextIndex); - }; - - const moveActiveIndex = (delta: number) => { - setActiveIndex((current) => { - const base = current ?? 0; - return Math.max(0, Math.min(items.length - 1, base + delta)); - }); - }; - - if (items.length < TIMELINE_MINIMAP_MIN_ITEMS) { - return null; - } - - return ( - - ); -} - -function TimelineMinimapNavigationButton({ - direction, - disabled, - interactive, - onClick, -}: { - direction: "previous" | "next"; - disabled: boolean; - interactive: boolean; - onClick: () => void; -}) { - const previous = direction === "previous"; - const label = previous ? "Previous turn" : "Next turn"; - const Icon = previous ? ChevronUpIcon : ChevronDownIcon; - - return ( - - - } - > - - - {label} - - ); -} - type TimelineWorkEntry = Extract["groupedEntries"][number]; type TimelineRow = MessagesTimelineRow; diff --git a/apps/web/src/components/chat/TimelineMinimap.tsx b/apps/web/src/components/chat/TimelineMinimap.tsx new file mode 100644 index 0000000000000..c4cc203e8a75f --- /dev/null +++ b/apps/web/src/components/chat/TimelineMinimap.tsx @@ -0,0 +1,335 @@ +import { resolveTimelineMinimapPreview, type TimelineMinimapItem } from "./timelineMinimapItems"; +import { useEffect, useRef, useState, type MouseEvent } from "react"; +import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import { Button } from "../ui/button"; +import { + resolveTimelineMinimapHeightStyle, + resolveTimelineMinimapIndexFromPointer, + resolveTimelineMinimapInteractiveWidth, + resolveTimelineMinimapNavigationInteractive, + resolveTimelineMinimapTopPercent, + TIMELINE_MINIMAP_MIN_ITEMS, +} from "./MessagesTimeline.logic"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { cn } from "~/lib/utils"; + +function timelineMinimapEventTargetsPreview(target: EventTarget): boolean { + return target instanceof Element && target.closest("[data-minimap-preview]") !== null; +} + +// A release outside the window can skip `mouseup`, so the next button-free +// `mousemove` also counts. +function onMouseButtonsReleased(onRelease: () => void): () => void { + const check = (event: globalThis.MouseEvent) => { + if (event.buttons !== 0) return; + stop(); + onRelease(); + }; + const stop = () => { + window.removeEventListener("mouseup", check); + window.removeEventListener("mousemove", check); + }; + window.addEventListener("mouseup", check); + window.addEventListener("mousemove", check); + return stop; +} + +export function TimelineMinimap({ + hasPersistentGutter, + hitStripWidth, + currentIndex, + items, + stripMap, + onWheel, + onSelect, +}: { + hasPersistentGutter: boolean; + hitStripWidth: number; + currentIndex: number | null; + items: ReadonlyArray; + stripMap: Map; + onWheel: (event: WheelEvent) => void; + onSelect: (item: TimelineMinimapItem) => void; +}) { + const [activeIndex, setActiveIndex] = useState(null); + const wheelTargetRef = (node: HTMLDivElement | null) => { + if (!node) return; + node.addEventListener("wheel", onWheel, { passive: false }); + return () => node.removeEventListener("wheel", onWheel); + }; + + const pressedOnStripRef = useRef(false); + const [passThrough, setPassThrough] = useState(false); + + useEffect(() => { + if (!passThrough) return; + return onMouseButtonsReleased(() => setPassThrough(false)); + }, [passThrough]); + + const resolvedActiveIndex = + activeIndex !== null && activeIndex < items.length ? activeIndex : null; + const activeItem = resolveTimelineMinimapPreview( + resolvedActiveIndex === null ? null : (items[resolvedActiveIndex] ?? null), + ); + const navigationInteractive = + resolveTimelineMinimapNavigationInteractive(hitStripWidth) && !passThrough; + const activeTopPercent = + resolvedActiveIndex === null + ? 0 + : resolveTimelineMinimapTopPercent(resolvedActiveIndex, items.length); + const activeTooltipTranslate = + resolvedActiveIndex === null + ? "-50%" + : resolvedActiveIndex === 0 + ? "0%" + : resolvedActiveIndex === items.length - 1 + ? "-100%" + : "-50%"; + const resolvedCurrentIndex = + currentIndex !== null && currentIndex >= 0 && currentIndex < items.length ? currentIndex : null; + const previousItem = + resolvedCurrentIndex === null ? null : (items[resolvedCurrentIndex - 1] ?? null); + const nextItem = resolvedCurrentIndex === null ? null : (items[resolvedCurrentIndex + 1] ?? null); + + const resolveActiveIndexFromPointer = (event: MouseEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + return resolveTimelineMinimapIndexFromPointer({ + itemCount: items.length, + railTop: rect.top, + railHeight: rect.height, + pointerY: event.clientY, + }); + }; + + const updateActiveIndexFromPointer = (event: MouseEvent) => { + const nextIndex = resolveActiveIndexFromPointer(event); + setActiveIndex(nextIndex); + }; + + const moveActiveIndex = (delta: number) => { + setActiveIndex((current) => { + const base = current ?? 0; + return Math.max(0, Math.min(items.length - 1, base + delta)); + }); + }; + + if (items.length < TIMELINE_MINIMAP_MIN_ITEMS) { + return null; + } + + return ( + + ); +} + +function TimelineMinimapNavigationButton({ + direction, + disabled, + interactive, + onClick, +}: { + direction: "previous" | "next"; + disabled: boolean; + interactive: boolean; + onClick: () => void; +}) { + const previous = direction === "previous"; + const label = previous ? "Previous turn" : "Next turn"; + const Icon = previous ? ChevronUpIcon : ChevronDownIcon; + + return ( + + + } + > + + + {label} + + ); +} From bea879025debc5b3f0c6c62949e3d2cb02ffc6e5 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:49 -0700 Subject: [PATCH 009/179] refactor(web): timeline work entry rows and tool icons live in their own files (#22674) Co-authored-by: Claude Opus 5.5 --- .../chat/MessagesTimeline.browser.test.tsx | 3 +- .../src/components/chat/MessagesTimeline.tsx | 1242 +---------------- .../src/components/chat/TimelineRowParts.tsx | 55 + .../chat/TimelineToolActivityIcon.tsx | 272 ++++ .../chat/TimelineWorkEntryRow.logic.ts | 236 ++++ .../components/chat/TimelineWorkEntryRow.tsx | 617 ++++++++ .../src/components/chat/timelineRowContext.ts | 99 ++ 7 files changed, 1321 insertions(+), 1203 deletions(-) create mode 100644 apps/web/src/components/chat/TimelineRowParts.tsx create mode 100644 apps/web/src/components/chat/TimelineToolActivityIcon.tsx create mode 100644 apps/web/src/components/chat/TimelineWorkEntryRow.logic.ts create mode 100644 apps/web/src/components/chat/TimelineWorkEntryRow.tsx create mode 100644 apps/web/src/components/chat/timelineRowContext.ts diff --git a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx index df846a5e7bf5a..ddf8d87049faa 100644 --- a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx @@ -6,7 +6,8 @@ import { describe, expect, it, vi } from "vite-plus/test"; import "~/index.css"; import { act, render } from "../../../test/render"; -import { MessagesTimeline, remarkThoughtPreview } from "./MessagesTimeline"; +import { MessagesTimeline } from "./MessagesTimeline"; +import { remarkThoughtPreview } from "./TimelineWorkEntryRow.logic"; import { parseMarkdownWithMath } from "@t3tools/shared/markdownMath"; const pullRequestQuery = vi.hoisted(() => ({ error: "Pull request not found" })); diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index bb7f613602f56..2bcd94805f266 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -1,31 +1,27 @@ import { useRenderedAppearance } from "../../hooks/useTheme"; -import type { Root, RootContent } from "mdast"; import { ThreadFindTimelineContext } from "./ThreadFindProvider"; import { shallow } from "zustand/vanilla/shallow"; import { shouldPreserveAssistantLineBreaks } from "@t3tools/shared/markdownPipeline"; import { MarkdownFindContext, findRevealRef } from "./markdownFindContext"; import { useChatCanvas } from "./ChatCanvasContext"; -import { WorkLogBlock, WorkLogButton, WorkLogDetails, WorkLogList, WorkLogRow } from "./WorkLog"; +import { WorkLogBlock, WorkLogButton, WorkLogList, WorkLogRow } from "./WorkLog"; import { PullRequestGlyph } from "../pullRequest/pullRequestIcons"; import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; import { ReadOnlySourcePreview } from "../files/AttachmentFilePreview"; import { useRightPanelStore } from "~/rightPanelStore"; import { getQuestionAnswerPreview, - getQuestionAnswerText, getQuestionTextPreview, hasQuestionAnswer, } from "@t3tools/client-runtime/work-log/user-input"; import { deriveTimelineMinimapItems } from "./timelineMinimapItems"; import { COMPOSER_CONTEXT_KINDS, - ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE, type AssistantCitation, type EnvironmentId, type MessageId, type OrchestrationV2TurnItem, type RunAttemptId, - type ScopedThreadRef, type ServerProviderSkill, type RunId, ThreadId, @@ -37,19 +33,9 @@ import { environmentThreadDetails } from "../../state/threads"; import { resolveUserMessagePresentation } from "@t3tools/client-runtime/user-message"; import { Link } from "@tanstack/react-router"; import { canForkProjectedAssistantItem } from "@t3tools/client-runtime/state/thread-workflows"; -import { notificationChildThreadId } from "@t3tools/client-runtime/state/thread-execution"; import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; -import { - liveThoughtLine, - resolveWorkEntryToolPresentation, - resolveViewedImageAsset, - workEntryViewedImagePath, -} from "@t3tools/client-runtime/work-log/presentation"; +import { liveThoughtLine } from "@t3tools/client-runtime/work-log/presentation"; import { resolveWorkGroupScrollAnchor } from "@t3tools/client-runtime/work-log/scroll-anchor"; -import { - turnItemHasDetail, - turnItemNeedsDetailFetch, -} from "@t3tools/client-runtime/work-log/item-detail"; import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments"; import { subagentGroupSummary, @@ -63,13 +49,9 @@ const NOOP_USE_ARTIFACT_TEMPLATE = () => {}; const NOOP_OPEN_ATTACHMENT = (_attachment: ChatFileAttachment) => {}; import { resolveChatListAnchoredEndSpace } from "@t3tools/shared/chatList"; -import { toolActivityFaviconUrl } from "@t3tools/shared/favicon"; import { formatDuration } from "@t3tools/shared/orchestrationTiming"; -import { getProjectFaviconCacheKey } from "@t3tools/shared/projectFavicon"; -import { claudeSkillInvocation, isJavaScriptReplTool } from "@t3tools/shared/toolActivity"; import { observeVisibleAnimation } from "../../lib/visibleAnimation"; import { - createContext, memo, use, useContext, @@ -77,8 +59,6 @@ import { useLayoutEffect, useRef, useState, - type KeyboardEvent, - type MouseEvent, type ReactNode, } from "react"; import { @@ -94,8 +74,6 @@ import { createMessageAttachmentPreviewProjector, selectMessageImageResources, workEntryDisplayIndicatesToolFailure, - workEntrySignalsSevereFailure, - workLogEntryIsToolLike, } from "../../session-logic"; import type { CodexArtifactTemplate } from "@t3tools/shared/codexArtifactTemplates"; import { @@ -113,21 +91,17 @@ import { resolveFileDiffPath, } from "../../lib/diffRendering"; import { PREFERRED_HIGHLIGHTER } from "../../lib/syntaxHighlighting"; -import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown"; -import { T3Wordmark } from "../T3Wordmark"; +import ChatMarkdown from "../ChatMarkdown"; import { ThreadContextChip } from "../ThreadContextChip"; import { ArrowRightIcon, BotIcon, - BrainIcon, CheckIcon, ChevronDownIcon, - ChevronRightIcon, CircleAlertIcon, DownloadIcon, EyeIcon, GitForkIcon, - GlobeIcon, type LucideIcon, MessageCircleIcon, MousePointerClickIcon, @@ -135,17 +109,10 @@ import { MinusIcon, Redo2Icon, Minimize2Icon, - SearchIcon, - SmartphoneIcon, - SquarePenIcon, - TerminalIcon, Undo2Icon, - HammerIcon, WrenchIcon, - Code2Icon, XIcon, ZapIcon, - RotateCcwIcon, } from "lucide-react"; import { ChevronDown, ChevronRight, Link as LinkIconNode } from "lucide"; import type { @@ -188,11 +155,7 @@ import { PierreEntryIcon } from "./PierreEntryIcon"; import { WorkspaceEntryIcon, WorkspaceEntryTooltip } from "./WorkspaceEntryIcon"; import { AssistantSelectionToolbar } from "./AssistantSelectionToolbar"; import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection"; -import { - AssistantCitationSource, - type AssistantCitationRequest, - type AssistantCitationTarget, -} from "./AssistantCitationSource"; +import { AssistantCitationSource, type AssistantCitationRequest } from "./AssistantCitationSource"; import { useAssistantCitationTarget, type CitationHistoryPage } from "./useAssistantCitationTarget"; import { computeStableMessagesTimelineRows, @@ -210,18 +173,11 @@ import { shouldCollapseUserMessage, shouldFollowWorkGroupAppend, timelineTurnFoldRunIdsByEntryId, - threadReadLabelPrefix, - threadReadTargetId, - threadReadTargetTitle, - toolGroupAction, - workEntryDisplayLabel, - workEntryReadOutput, workEntryIsVisibleInGroup, worktreeSetupAgentStarted, type StableMessagesTimelineRowsState, type MessagesTimelineRow, type TimelineLatestRun, - type WorkGroupScrollAnchor, } from "./MessagesTimeline.logic"; import { type ThreadFindMatch } from "./threadFind"; import { useThreadFindNavigation } from "./useThreadFindNavigation"; @@ -269,19 +225,14 @@ import { useMediaQuery } from "~/hooks/useMediaQuery"; import { cn } from "~/lib/utils"; import { useUiStateStore } from "~/uiStateStore"; import { type TimestampFormat } from "@t3tools/contracts/settings"; -import { - formatChatTimestampTooltip, - formatDayAwareTimestamp, - formatUpcomingTimestamp, -} from "../../timestampFormat"; -import { FetchedToolOutput, V2ItemInspector } from "./V2ItemInspector"; +import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; +import { V2ItemInspector } from "./V2ItemInspector"; import { useV2ItemSupport } from "../../state/v2ItemSupport"; import { Collapsible, CollapsibleTrigger, CollapsiblePanel } from "../ui/collapsible"; import { isV2LifecycleItem, EnvironmentSubagentAvatar, SubagentElapsed, - SubagentNotificationLink, V2LifecycleRow, type HandoffTimelineRun, } from "./V2LifecycleRow"; @@ -299,76 +250,41 @@ import { type ReviewCommentContext, } from "../../reviewCommentContext"; import { TimelineMinimap } from "./TimelineMinimap"; +import { + type TimelineRowSharedState, + type TimelineRowActivityState, + TimelineRowCtx, + TimelineRowActivityCtx, + type WorkGroupViewState, + WorkGroupViewCtx, + type TimelineWorkEntry, + type TimelineRow, +} from "./timelineRowContext"; +import { + MESSAGE_HEADING_LEVEL, + MessageAuthorHeading, + TimelineRowTimestamp, + ActivityShimmerOverlay, +} from "./TimelineRowParts"; +import { + failedToolIconClassName, + toolIconAcceptsTint, + type WorkEntryIconName, + ToolActivityIconView, +} from "./TimelineToolActivityIcon"; +import { + useThreadReadTarget, + threadReadLabel, + ThreadReadLabel, + ReasoningTraceContent, + SimpleWorkEntryRow, +} from "./TimelineWorkEntryRow"; +import { + remarkThoughtPreview, + toolGroupSummaryIconName, + workEntryIconName, +} from "./TimelineWorkEntryRow.logic"; -interface TimelineRowSharedState { - citationRequest: AssistantCitationTarget | null; - listRef: React.RefObject; - timestampFormat: TimestampFormat; - routeThreadKey: string; - threadRef: ScopedThreadRef | null; - markdownCwd: string | undefined; - resolvedTheme: "light" | "dark"; - workspaceRoot: string | undefined; - skills: ReadonlyArray>; - runs: ReadonlyArray; - activeThreadEnvironmentId: EnvironmentId; - onRevertToTurnCount: (targetTurnCount: number, messageId: MessageId) => void; - onUseArtifactTemplate: (template: CodexArtifactTemplate) => void; - onSendAppMessage: ((text: string) => Promise) | undefined; - onAppFullscreenChange: (rowId: string, fullscreen: boolean) => void; - onRunShellCommand: ((command: string) => void) | undefined; - findActive: boolean; - onImageExpand: (preview: ExpandedImagePreview) => void; - displayThreadKey?: string; - onOpenTurnDiff: (runId: RunId, filePath?: string) => void; - onOpenThread: (threadId: OrchestrationV2TurnItem["threadId"]) => void; - workflowOpening: WorkflowOpening | null; - onForkFromRun: (input: { - readonly sourceThreadId: ThreadId; - readonly runId: RunId; - }) => Promise; - onRollbackCheckpoint: (input: { - readonly checkpointId: string; - readonly scopeId: string; - }) => void; - onToggleTurnFold: (runId: RunId) => void; - onToggleAttemptFold: (attemptId: RunAttemptId) => void; - onFileOpen: (attachment: ChatFileAttachment) => void; - onFileDownload: (attachment: ChatFileAttachment) => void; - openPullRequest: (event: MouseEvent, url: string) => void; - onToggleWorkGroup: (groupId: string, anchorKey: string) => void; - onToggleWorkEntry: (anchorKey: string, collapsed: boolean) => void; - onCancelWorktreeSetup: (() => void) | null; - retryableWorkspacePreparationRunIds: ReadonlySet; - onRetryWorkspacePreparation: ((runId: RunId) => void) | null; - onWorktreeSetupWorkLocally: (() => void) | null; - onOpenWorktreeSetupTerminal: ((terminalId: string) => void) | null; - workGroupViewState: WorkGroupViewState; -} - -interface TimelineRowActivityState { - isWorking: boolean; - isCompacting: boolean; - isRevertingCheckpoint: boolean; - activeTurnInProgress: boolean; - awaitingUser: boolean; - isPreparingWorktree: boolean; - latestRunId: RunId | null; - backgroundWorktreeSetup: WorktreeSetupSnapshot | null; -} - -const TimelineRowCtx = createContext(null!); -const TimelineRowActivityCtx = createContext(null!); - -interface WorkGroupViewState { - scrollPositions: Map; - expandedEntries: Set; -} - -const WorkGroupViewCtx = createContext<{ - state: WorkGroupViewState; - onToggleEntry: (collapsed: boolean) => void; -} | null>(null); function timelineListStateFor(identity: string): { readonly identity: string; readonly rememberedPosition: RememberedTimelinePosition | undefined; @@ -1620,9 +1536,6 @@ function getItemType(item: MessagesTimelineRow) { return `message:${item.message.role}:${lengthBucket}`; } -type TimelineWorkEntry = Extract["groupedEntries"][number]; -type TimelineRow = MessagesTimelineRow; - const TimelineRowContent = memo(function TimelineRowContent({ row }: { row: TimelineRow }) { const isExpandedToolGroup = row.kind === "work" && row.isExpandedToolGroup; const isSubagentGroup = row.kind === "event" && row.projectedItem.item.type === "subagent"; @@ -1810,12 +1723,6 @@ function UserVideoAttachment({ file }: { readonly file: ChatFileAttachment }) { ); } -const MESSAGE_HEADING_LEVEL = 3; - -function MessageAuthorHeading({ children }: { children: string }) { - return

{children}

; -} - function UserMessageRow({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); return ctx.workflowOpening?.messageId === row.message.id ? ( @@ -2253,37 +2160,6 @@ function RevertUserMessageButton({ ); } -function TimelineRowTimestamp({ - createdAt, - timestampFormat, - className, - alwaysVisible = false, -}: { - createdAt: string; - timestampFormat: TimestampFormat; - className?: string; - alwaysVisible?: boolean; -}) { - return ( - - - } - > - {formatDayAwareTimestamp(createdAt, timestampFormat)} - - {formatChatTimestampTooltip(createdAt, timestampFormat)} - - ); -} - function TurnFoldTimelineRow({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); @@ -3240,25 +3116,6 @@ function ExpandedWorkGroupEntries({ const workEntryKey = (entry: TimelineWorkEntry) => entry.id; -function ActivityShimmerOverlay({ children }: { children: ReactNode }) { - return ( - - - {children} - - - ); -} - -const failedToolIconClassName = "text-tool-error-icon/40"; - -function toolIconAcceptsTint(toolIcon: ToolActivityIcon | undefined): boolean { - return toolIcon === undefined; -} - function WorkingTimelineRow({ row }: { row: Extract }) { const { isCompacting, isPreparingWorktree, backgroundWorktreeSetup } = use(TimelineRowActivityCtx); @@ -3458,49 +3315,6 @@ function LiveActivityContent({ ); } -function useThreadReadTarget(entry: TimelineWorkEntry, environmentId: EnvironmentId) { - const rawThreadId = threadReadTargetId(entry); - const threadId = rawThreadId === null ? null : ThreadId.make(rawThreadId); - const shell = useThreadShell(threadId ? scopeThreadRef(environmentId, threadId) : null); - const title = threadReadTargetTitle(shell); - return threadId && title ? { threadId, title } : null; -} - -function threadReadLabel(label: string, target: ReturnType) { - const prefix = target && threadReadLabelPrefix(label); - return prefix ? { ...target, prefix, text: `${prefix} “${target.title}”` } : null; -} - -function ThreadReadLabel({ - label, - environmentId, - linked, -}: { - label: NonNullable>; - environmentId: EnvironmentId; - linked: boolean; -}) { - return ( - - {label.prefix} “ - {linked ? ( - - {label.title} - - ) : ( - {label.title} - )} - ” - - ); -} - function LiveWorkEntryTimelineRow({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); const threadTarget = useThreadReadTarget(row.entry, ctx.activeThreadEnvironmentId); @@ -3597,49 +3411,6 @@ function LiveWorkEntryTimelineRow({ row }: { row: Extract["summaryKind"], -): WorkEntryIconName { - switch (kind) { - case "pull-request": - case "link-pr": - case "unlink-pr": - case "list-prs": - case "watch-pr": - case "unwatch-pr": - return "pull-request"; - case "read": - return "eye"; - case "edit": - return "square-pen"; - case "command": - return "terminal"; - case "thread-create": - return "t3-code"; - case "browser": - return "browser"; - case "device": - return "device"; - case "search": - return "globe"; - case "code-search": - return "search"; - case "other": - return "wrench"; - case "dynamic-tool": - return "hammer"; - case "reasoning": - return "brain"; - case "agent-tool": - return "bot"; - case "tone-tool": - return "zap"; - case "update": - case "mixed": - return "hammer"; - } -} - function WorkGroupToggleTimelineRow({ row, }: { @@ -4506,936 +4277,3 @@ function useStableRows(rows: MessagesTimelineRow[], identity: string): MessagesT setState({ identity, source: rows, stable }); return stable.result; } - -type WorkEntryIconName = - | "bot" - | "brain" - | "browser" - | "check" - | "circle-alert" - | "computer" - | "code" - | "device" - | "eye" - | "globe" - | "hammer" - | "message-circle" - | "search" - | "square-pen" - | "terminal" - | "pull-request" - | "t3-code" - | "wrench" - | "x" - | "zap"; - -function BrowserAppIcon({ className }: { className: string }) { - return ( - - - - - - - ); -} - -function ToolActivityIconView(props: { - icon: ToolActivityIcon | undefined; - fallbackName: WorkEntryIconName; - className: string; - muted: boolean; -}) { - const { resolvedTheme } = use(TimelineRowCtx); - const fallbackClassName = cn(props.className, props.muted && "opacity-70 light:brightness-60"); - if (!props.icon) { - return ; - } - if (props.icon._tag === "website") { - const src = toolActivityFaviconUrl(props.icon, resolvedTheme, 32); - return src ? ( - - ) : ( - - ); - } - if (props.icon._tag === "themed-logo") { - const src = - resolvedTheme === "dark" - ? (props.icon.logoUrlDark ?? props.icon.logoUrl) - : props.icon.logoUrl; - return ( - - ); - } - return ( - - ); -} - -function NativeAppToolActivityIcon(props: { - app: Extract["app"]; - fallbackName: WorkEntryIconName; - className: string; - muted: boolean; -}) { - const { activeThreadEnvironmentId } = use(TimelineRowCtx); - const asset = useAssetUrlState(activeThreadEnvironmentId, { - _tag: "native-app-icon", - app: props.app, - }); - if (asset._tag !== "Success") { - return ( - - ); - } - const cacheKey = getProjectFaviconCacheKey( - activeThreadEnvironmentId, - JSON.stringify(props.app), - asset.url, - ); - return ( - - ); -} - -const loadedToolActivityIconSrcs = new Map(); - -function ToolActivityImageIcon(props: { - cacheKey: string; - src: string; - fallbackName: WorkEntryIconName; - className: string; - muted: boolean; -}) { - const [displayedSrc, setDisplayedSrc] = useState( - () => loadedToolActivityIconSrcs.get(props.cacheKey) ?? null, - ); - const isLoading = displayedSrc !== props.src; - const handleLoadError = (failedSrc: string) => { - if (loadedToolActivityIconSrcs.get(props.cacheKey) === failedSrc) { - loadedToolActivityIconSrcs.delete(props.cacheKey); - } - setDisplayedSrc((currentSrc) => (currentSrc === failedSrc ? null : currentSrc)); - }; - return ( - <> - {displayedSrc === null ? ( - - ) : null} - {displayedSrc ? ( - - handleLoadError(displayedSrc)} - /> - - ) : null} - {isLoading ? ( - { - loadedToolActivityIconSrcs.set(props.cacheKey, props.src); - setDisplayedSrc(props.src); - }} - onError={() => handleLoadError(props.src)} - /> - ) : null} - - ); -} - -function WorkEntryIcon({ name, className }: { name: WorkEntryIconName; className: string }) { - switch (name) { - case "pull-request": - return ; - case "bot": - return ; - case "brain": - return ; - case "browser": - case "computer": - return ; - case "code": - return ; - case "device": - return ; - case "t3-code": - return ; - case "check": - return ; - case "circle-alert": - return ; - case "eye": - return ; - case "globe": - return ; - case "hammer": - return ; - case "search": - return ; - case "message-circle": - return ; - case "square-pen": - return ; - case "terminal": - return ; - case "wrench": - return ; - case "x": - return ; - case "zap": - return ; - } -} - -function workToneIcon(tone: TimelineWorkEntry["tone"]): { - iconName: WorkEntryIconName; - className: string; -} { - if (tone === "error") { - return { - iconName: "circle-alert", - className: "text-foreground/92", - }; - } - if (tone === "thinking") { - return { - iconName: "brain", - className: "text-icon-muted", - }; - } - if (tone === "info") { - return { - iconName: "check", - className: "text-muted-foreground", - }; - } - return { - iconName: "zap", - className: "text-foreground/92", - }; -} - -function workEntryRawCommand( - workEntry: Pick, -): string | null { - const rawCommand = workEntry.rawCommand?.trim(); - if (!rawCommand || !workEntry.command) { - return null; - } - return rawCommand === workEntry.command.trim() ? null : rawCommand; -} - -function buildToolCallExpandedBody( - workEntry: TimelineWorkEntry, - workspaceRoot: string | undefined, - visibleLabel: string, - viewedImagePath: string | null, -): string | null { - const blocks: string[] = []; - const seen = new Set([visibleLabel.trim()]); - const addBlock = (value: string | null | undefined) => { - const text = value?.trim(); - if (!text || seen.has(text)) return; - seen.add(text); - blocks.push(text); - }; - if (workEntry.itemType === "dynamic_tool" && workEntry.toolData !== undefined) { - const input = - workEntry.structuredPayload?.type === "dynamic_tool" - ? workEntry.structuredPayload.input - : workEntry.toolData !== null && - typeof workEntry.toolData === "object" && - "input" in workEntry.toolData - ? workEntry.toolData.input - : undefined; - if (input !== undefined) addBlock(`Tool input\n${JSON.stringify(input, null, 2)}`); - } - const command = workEntry.command?.trim(); - const raw = workEntryRawCommand(workEntry); - if (command === visibleLabel.trim()) { - seen.add(command); - } else { - addBlock(raw ?? command); - } - const detail = workEntry.detail?.trim(); - if (detail !== viewedImagePath?.trim()) { - addBlock(detail); - } - const viewedImagePaths = new Set( - viewedImagePath - ? [viewedImagePath.trim(), formatWorkspaceRelativePath(viewedImagePath, workspaceRoot)] - : [], - ); - const changedFiles = (workEntry.changedFiles ?? []).flatMap((filePath) => { - const formattedPath = formatWorkspaceRelativePath(filePath, workspaceRoot); - return viewedImagePaths.has(filePath) || - viewedImagePaths.has(formattedPath) || - filePath.trim() === detail || - formattedPath === detail - ? [] - : [formattedPath]; - }); - if (changedFiles.length > 0) { - addBlock([...new Set(changedFiles)].join("\n")); - } - return blocks.length > 0 ? blocks.join("\n\n") : null; -} - -const toolCallExpandedBodyClassName = - "max-h-64 cursor-text overflow-auto whitespace-pre-wrap break-words font-mono text-secondary-label text-(length:--font-size-code,var(--text-2xs)) leading-relaxed select-text"; - -function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName { - if (workEntry.structuredPayload?.type === "notification") { - if (workEntry.structuredPayload.outcome === "failed") return "circle-alert"; - const source = workEntry.structuredPayload.source; - switch (source.kind) { - case "subagent": - case "delegated_task": - return "bot"; - case "command": - return "terminal"; - case "monitor": - return "eye"; - case "background_task": - return "zap"; - case "system": - return "t3-code"; - default: - source satisfies never; - return "zap"; - } - } - if (workEntry.itemType === "user_input_request" || workEntry.itemType === "approval_request") { - return "message-circle"; - } - if (workEntry.toolSurface) return workEntry.toolSurface; - if ( - workEntry.structuredPayload?.type === "dynamic_tool" && - isJavaScriptReplTool(workEntry.structuredPayload.toolName) - ) - return "code"; - const toolPresentation = resolveWorkEntryToolPresentation(workEntry); - if (toolPresentation) return toolPresentation.icon; - const action = toolGroupAction(workEntry); - if (action !== "other") return toolGroupSummaryIconName(action); - - switch (workEntry.itemType) { - case "dynamic_tool": - return "wrench"; - case "subagent": - return "bot"; - } - - return workToneIcon(workEntry.tone).iconName; -} - -const stopRowToggle = (e: { stopPropagation: () => void }) => e.stopPropagation(); - -/** Compact reasoning keeps formulas while flattening other Markdown into label text. */ -export function remarkThoughtPreview(fallback: string) { - return (tree: Root) => { - const children: RootContent[] = []; - const append = (value: string) => { - const last = children.at(-1); - const text = value.replace(/\s+/g, " "); - if (last?.type === "text") last.value = (last.value + text).replace(/\s+/g, " "); - else children.push({ type: "text", value: text }); - }; - const visit = (node: Root | RootContent) => { - if (node.type === "html" || node.type === "definition") return; - if (node.type === "math" || node.type === "inlineMath") { - children.push({ - type: "inlineMath", - value: node.value, - position: node.position, - data: { - hName: "code", - hProperties: { className: ["language-math", "math-inline"] }, - hChildren: [{ type: "text", value: node.value }], - }, - }); - } else if ("alt" in node) append(node.alt ?? ""); - else if ("value" in node) append(node.value); - else if ("children" in node) { - node.children.forEach((child, index) => { - if ( - index && - ["root", "blockquote", "list", "listItem", "table", "tableRow"].includes(node.type) - ) - append(" "); - visit(child); - }); - } else if (node.type === "break") append(" "); - }; - visit(tree); - const first = children[0]; - const last = children.at(-1); - if (first?.type === "text") first.value = first.value.trimStart(); - if (last?.type === "text") last.value = last.value.trimEnd(); - tree.children = children.some((child) => child.type !== "text" || child.value) - ? children - : [{ type: "text", value: fallback }]; - }; -} - -function ReasoningTraceContent({ entries }: { entries: ReadonlyArray }) { - const ctx = use(TimelineRowCtx); - const { isWorking, latestRunId } = use(TimelineRowActivityCtx); - const isStreaming = (entry: TimelineWorkEntry) => - isWorking && entry.runId === latestRunId && entry.toolLifecycleStatus === "inProgress"; - return ( - - {entries.map((entry) => ( - - ))} - - ); -} - -type WorkEntryRowProps = { - workEntry: TimelineWorkEntry; - workspaceRoot: string | undefined; - displayLabel?: string | undefined; - onToggleEntry?: ((collapsed: boolean) => void) | undefined; -}; - -const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: WorkEntryRowProps) { - const ctx = use(TimelineRowCtx); - const item = props.workEntry.projectedItem?.item; - const childThreadId = - item?.type === "notification" ? notificationChildThreadId(item.source) : undefined; - if (item?.type !== "notification" || childThreadId === undefined) { - return ; - } - return ( - } - /> - ); -}); - -function WorkEntryLogRow(props: WorkEntryRowProps) { - const { workEntry, workspaceRoot, displayLabel } = props; - const ctx = use(TimelineRowCtx); - const { threadRef, onImageExpand, timestampFormat } = ctx; - const { retryableWorkspacePreparationRunIds, onRetryWorkspacePreparation } = ctx; - const threadTarget = useThreadReadTarget(workEntry, ctx.activeThreadEnvironmentId); - const createdThread = - workEntry.projectedItem?.item.type === "thread_created" - ? workEntry.projectedItem.item - : undefined; - const notifiedSubagentThreadId = - workEntry.projectedItem?.item.type === "notification" - ? notificationChildThreadId(workEntry.projectedItem.item.source) - : undefined; - const groupView = use(WorkGroupViewCtx); - const [expanded, setExpanded] = useState( - () => groupView?.state.expandedEntries.has(workEntry.id) ?? false, - ); - const toggleExpanded = () => { - const next = !expanded; - if (groupView) { - groupView.onToggleEntry(!next); - if (next) groupView.state.expandedEntries.add(workEntry.id); - else groupView.state.expandedEntries.delete(workEntry.id); - } else { - props.onToggleEntry?.(!next); - } - setExpanded(next); - }; - const failureItem = workEntry.projectedItem?.item; - if (failureItem?.type === "error" && failureItem.status === "failed") { - const warning = failureItem.failure.class === "usage_limit"; - const resetAt = failureItem.failure.resetAt; - const resetTime = resetAt ? formatUpcomingTimestamp(resetAt, timestampFormat) : null; - const label = warning - ? `Usage limit reached.${resetTime ? ` Retry after ${resetTime}.` : ""}` - : workEntry.label; - const retryRunId = - failureItem.runId !== null && - retryableWorkspacePreparationRunIds.has(failureItem.runId) && - failureItem.failure.code === ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE - ? failureItem.runId - : null; - return ( - - } - label={ - - {label} - - } - trailing={ - - } - > - {!warning ? ( -

- {failureItem.failure.message} -

- ) : null} - {retryRunId !== null && onRetryWorkspacePreparation ? ( -
- -
- ) : null} -
- ); - } - const iconConfig = workToneIcon(workEntry.tone); - const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning"; - const showFailedIndicator = - !showWarningIndicator && workEntryDisplayIndicatesToolFailure(workEntry); - const showDestructiveRowStyle = - !showWarningIndicator && - showFailedIndicator && - (workEntrySignalsSevereFailure(workEntry) || !workLogEntryIsToolLike(workEntry)); - const entryIconName = - showWarningIndicator || showDestructiveRowStyle ? "circle-alert" : workEntryIconName(workEntry); - const entryToolIcon = - showWarningIndicator || showDestructiveRowStyle - ? undefined - : (workEntry.toolIcon ?? workEntry.toolSource?.icon); - const isReasoning = workEntry.itemType === "reasoning"; - const questionHeading = workEntry.questionAnswer - ? getQuestionTextPreview(workEntry.questionAnswer) - : ""; - const previewText = - isReasoning && expanded - ? workEntry.toolLifecycleStatus === "inProgress" - ? "Thinking" - : "Thought" - : questionHeading || (displayLabel ?? workEntryDisplayLabel(workEntry, workspaceRoot)); - const answerPreview = - workEntry.questionAnswer && hasQuestionAnswer(workEntry.questionAnswer) - ? getQuestionAnswerPreview(workEntry.questionAnswer) - : null; - const viewedImagePath = workEntryViewedImagePath(workEntry); - const payload = workEntry.structuredPayload; - const skill = - payload?.type === "dynamic_tool" - ? claudeSkillInvocation(payload.toolName, payload.input) - : undefined; - const plainOutput = - toolGroupAction(workEntry) === "read" - ? workEntryReadOutput(workEntry, workspaceRoot) - : skill - ? (skill.args ?? null) - : undefined; - const viewedImage = - viewedImagePath && threadRef - ? resolveViewedImageAsset(viewedImagePath, { - threadId: threadRef.threadId, - workspaceRoot, - }) - : null; - const canExpand = - previewText.trim().length > 0 || - (workEntry.itemType === "dynamic_tool" && workEntry.toolData !== undefined) || - Boolean( - workEntryRawCommand(workEntry) || - workEntry.command?.trim() || - workEntry.detail?.trim() || - workEntry.changedFiles?.length || - viewedImage, - ); - const expandedBody = - expanded && !isReasoning - ? plainOutput !== undefined - ? plainOutput - : buildToolCallExpandedBody( - workEntry, - workspaceRoot, - previewText, - viewedImage ? viewedImagePath : null, - ) - : null; - const plainOutputFetches = - plainOutput !== undefined && - workEntry.projectedItem !== undefined && - turnItemNeedsDetailFetch(workEntry.projectedItem.item); - const canExpandProjectedItem = - plainOutput !== undefined - ? Boolean(plainOutput || viewedImage || workEntry.questionAnswer || plainOutputFetches) - : workEntry.projectedItem === undefined - ? canExpand - : isReasoning - ? Boolean(workEntry.detail?.trim()) - : Boolean( - viewedImage || - workEntry.questionAnswer || - turnItemHasDetail(workEntry.projectedItem.item), - ); - const iconWrapperClass = cn( - "flex size-4 items-center justify-center", - showWarningIndicator - ? "text-warning" - : showDestructiveRowStyle - ? "text-destructive" - : showFailedIndicator - ? failedToolIconClassName - : workEntry.tone === "tool" - ? "text-icon-muted" - : iconConfig.className, - ); - const headingClass = showWarningIndicator - ? "font-medium text-warning" - : showDestructiveRowStyle - ? "font-medium text-destructive" - : workLogEntryIsToolLike(workEntry) - ? "text-secondary-label" - : "text-foreground/80"; - const threadLabel = isReasoning ? null : threadReadLabel(previewText, threadTarget); - const accessiblePreview = [threadLabel?.text ?? previewText, answerPreview] - .filter(Boolean) - .join(": "); - const accessibleDisplayText = showFailedIndicator - ? `${accessiblePreview}, tool call failed` - : accessiblePreview; - const rowToggleProps = canExpandProjectedItem - ? { - role: "button" as const, - tabIndex: 0 as const, - "aria-label": accessibleDisplayText, - "aria-expanded": expanded, - onClick: toggleExpanded, - onKeyDown: (e: KeyboardEvent) => { - if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { - e.preventDefault(); - toggleExpanded(); - } - }, - } - : {}; - - return ( - - - - } - label={ -
-

- - {isReasoning && !expanded ? ( - - ) : threadLabel ? ( - - ) : ( - previewText - )} - - {answerPreview ? ( - - {answerPreview} - - ) : null} -

-
- } - trailing={ - <> - {createdThread ? ( - - ) : null} - {notifiedSubagentThreadId ? ( - { - event.stopPropagation(); - ctx.onOpenThread(notifiedSubagentThreadId); - }} - onKeyDown={stopRowToggle} - > - Open subagent - - ) : null} - {showFailedIndicator && - !showDestructiveRowStyle && - !toolIconAcceptsTint(entryToolIcon) ? ( - - ) : null} - - - - - - } - > - {expanded && viewedImage && threadRef ? ( - - - - ) : null} - {expanded && workEntry.questionAnswer ? ( - - ) : null} - {expanded && isReasoning ? : null} - {expanded && - !isReasoning && - !workEntry.questionAnswer && - canExpandProjectedItem && - (expandedBody || - plainOutputFetches || - (workEntry.projectedItem && plainOutput === undefined)) ? ( - - {workEntry.projectedItem && plainOutput === undefined ? ( - - ) : ( - <> - {expandedBody ? ( -
{expandedBody}
- ) : null} - {plainOutputFetches && workEntry.projectedItem ? ( - - ) : null} - - )} -
- ) : null} -
- ); -} - -function QuestionAnswerHistory({ - answer, -}: { - answer: import("@t3tools/contracts").UserInputAttachmentAnswerPayload; -}) { - const { activeThreadEnvironmentId } = use(TimelineRowCtx); - const attachments = Object.values(answer.attachmentsByQuestionId).flat(); - const resources = attachments.map((attachment) => ({ - _tag: "attachment" as const, - attachmentId: attachment.id, - })); - const urls = useAssetUrls(activeThreadEnvironmentId, resources); - return ( -
- {[ - ...new Set([ - ...Object.keys(answer.questionTextById ?? {}), - ...Object.keys(answer.answers), - ...Object.keys(answer.attachmentsByQuestionId), - ]), - ].map((questionId) => ( -
- {answer.questionTextById?.[questionId] ? ( -

- {answer.questionTextById[questionId]} -

- ) : null} - {getQuestionAnswerText(answer.answers[questionId]) ? ( -

- {getQuestionAnswerText(answer.answers[questionId])} -

- ) : null} -
- {(answer.attachmentsByQuestionId[questionId] ?? []).map((attachment) => { - const url = urls[attachments.indexOf(attachment)]; - return ( - - {attachment.type === "image" && url ? ( - {attachment.name} - ) : ( - attachment.name - )} - - ); - })} -
-
- ))} -
- ); -} diff --git a/apps/web/src/components/chat/TimelineRowParts.tsx b/apps/web/src/components/chat/TimelineRowParts.tsx new file mode 100644 index 0000000000000..549b9c7a422f9 --- /dev/null +++ b/apps/web/src/components/chat/TimelineRowParts.tsx @@ -0,0 +1,55 @@ +import { type ReactNode } from "react"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { cn } from "~/lib/utils"; +import { type TimestampFormat } from "@t3tools/contracts/settings"; +import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; + +export const MESSAGE_HEADING_LEVEL = 3; + +export function MessageAuthorHeading({ children }: { children: string }) { + return

{children}

; +} + +export function TimelineRowTimestamp({ + createdAt, + timestampFormat, + className, + alwaysVisible = false, +}: { + createdAt: string; + timestampFormat: TimestampFormat; + className?: string; + alwaysVisible?: boolean; +}) { + return ( + + + } + > + {formatDayAwareTimestamp(createdAt, timestampFormat)} + + {formatChatTimestampTooltip(createdAt, timestampFormat)} + + ); +} + +export function ActivityShimmerOverlay({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/apps/web/src/components/chat/TimelineToolActivityIcon.tsx b/apps/web/src/components/chat/TimelineToolActivityIcon.tsx new file mode 100644 index 0000000000000..8d85ed2eefd67 --- /dev/null +++ b/apps/web/src/components/chat/TimelineToolActivityIcon.tsx @@ -0,0 +1,272 @@ +import { PullRequestGlyph } from "../pullRequest/pullRequestIcons"; +import { type ToolActivityIcon } from "@t3tools/contracts"; +import { toolActivityFaviconUrl } from "@t3tools/shared/favicon"; +import { getProjectFaviconCacheKey } from "@t3tools/shared/projectFavicon"; +import { use, useState } from "react"; +import { T3Wordmark } from "../T3Wordmark"; +import { + BotIcon, + BrainIcon, + CheckIcon, + CircleAlertIcon, + EyeIcon, + GlobeIcon, + MessageCircleIcon, + SearchIcon, + SmartphoneIcon, + SquarePenIcon, + TerminalIcon, + HammerIcon, + WrenchIcon, + Code2Icon, + XIcon, + ZapIcon, +} from "lucide-react"; +import { useAssetUrlState } from "../../assets/assetUrls"; +import { cn } from "~/lib/utils"; +import { TimelineRowCtx } from "./timelineRowContext"; + +export const failedToolIconClassName = "text-tool-error-icon/40"; + +export function toolIconAcceptsTint(toolIcon: ToolActivityIcon | undefined): boolean { + return toolIcon === undefined; +} + +export type WorkEntryIconName = + | "bot" + | "brain" + | "browser" + | "check" + | "circle-alert" + | "computer" + | "code" + | "device" + | "eye" + | "globe" + | "hammer" + | "message-circle" + | "search" + | "square-pen" + | "terminal" + | "pull-request" + | "t3-code" + | "wrench" + | "x" + | "zap"; + +function BrowserAppIcon({ className }: { className: string }) { + return ( + + + + + + + ); +} + +export function ToolActivityIconView(props: { + icon: ToolActivityIcon | undefined; + fallbackName: WorkEntryIconName; + className: string; + muted: boolean; +}) { + const { resolvedTheme } = use(TimelineRowCtx); + const fallbackClassName = cn(props.className, props.muted && "opacity-70 light:brightness-60"); + if (!props.icon) { + return ; + } + if (props.icon._tag === "website") { + const src = toolActivityFaviconUrl(props.icon, resolvedTheme, 32); + return src ? ( + + ) : ( + + ); + } + if (props.icon._tag === "themed-logo") { + const src = + resolvedTheme === "dark" + ? (props.icon.logoUrlDark ?? props.icon.logoUrl) + : props.icon.logoUrl; + return ( + + ); + } + return ( + + ); +} + +function NativeAppToolActivityIcon(props: { + app: Extract["app"]; + fallbackName: WorkEntryIconName; + className: string; + muted: boolean; +}) { + const { activeThreadEnvironmentId } = use(TimelineRowCtx); + const asset = useAssetUrlState(activeThreadEnvironmentId, { + _tag: "native-app-icon", + app: props.app, + }); + if (asset._tag !== "Success") { + return ( + + ); + } + const cacheKey = getProjectFaviconCacheKey( + activeThreadEnvironmentId, + JSON.stringify(props.app), + asset.url, + ); + return ( + + ); +} + +const loadedToolActivityIconSrcs = new Map(); + +function ToolActivityImageIcon(props: { + cacheKey: string; + src: string; + fallbackName: WorkEntryIconName; + className: string; + muted: boolean; +}) { + const [displayedSrc, setDisplayedSrc] = useState( + () => loadedToolActivityIconSrcs.get(props.cacheKey) ?? null, + ); + const isLoading = displayedSrc !== props.src; + const handleLoadError = (failedSrc: string) => { + if (loadedToolActivityIconSrcs.get(props.cacheKey) === failedSrc) { + loadedToolActivityIconSrcs.delete(props.cacheKey); + } + setDisplayedSrc((currentSrc) => (currentSrc === failedSrc ? null : currentSrc)); + }; + return ( + <> + {displayedSrc === null ? ( + + ) : null} + {displayedSrc ? ( + + handleLoadError(displayedSrc)} + /> + + ) : null} + {isLoading ? ( + { + loadedToolActivityIconSrcs.set(props.cacheKey, props.src); + setDisplayedSrc(props.src); + }} + onError={() => handleLoadError(props.src)} + /> + ) : null} + + ); +} + +function WorkEntryIcon({ name, className }: { name: WorkEntryIconName; className: string }) { + switch (name) { + case "pull-request": + return ; + case "bot": + return ; + case "brain": + return ; + case "browser": + case "computer": + return ; + case "code": + return ; + case "device": + return ; + case "t3-code": + return ; + case "check": + return ; + case "circle-alert": + return ; + case "eye": + return ; + case "globe": + return ; + case "hammer": + return ; + case "search": + return ; + case "message-circle": + return ; + case "square-pen": + return ; + case "terminal": + return ; + case "wrench": + return ; + case "x": + return ; + case "zap": + return ; + } +} diff --git a/apps/web/src/components/chat/TimelineWorkEntryRow.logic.ts b/apps/web/src/components/chat/TimelineWorkEntryRow.logic.ts new file mode 100644 index 0000000000000..dc077090e4307 --- /dev/null +++ b/apps/web/src/components/chat/TimelineWorkEntryRow.logic.ts @@ -0,0 +1,236 @@ +import type { Root, RootContent } from "mdast"; +import { resolveWorkEntryToolPresentation } from "@t3tools/client-runtime/work-log/presentation"; +import { isJavaScriptReplTool } from "@t3tools/shared/toolActivity"; +import { toolGroupAction } from "./MessagesTimeline.logic"; +import { formatWorkspaceRelativePath } from "../../filePathDisplay"; +import { type TimelineWorkEntry, type TimelineRow } from "./timelineRowContext"; +import { type WorkEntryIconName } from "./TimelineToolActivityIcon"; + +export function toolGroupSummaryIconName( + kind: Extract["summaryKind"], +): WorkEntryIconName { + switch (kind) { + case "pull-request": + case "link-pr": + case "unlink-pr": + case "list-prs": + case "watch-pr": + case "unwatch-pr": + return "pull-request"; + case "read": + return "eye"; + case "edit": + return "square-pen"; + case "command": + return "terminal"; + case "thread-create": + return "t3-code"; + case "browser": + return "browser"; + case "device": + return "device"; + case "search": + return "globe"; + case "code-search": + return "search"; + case "other": + return "wrench"; + case "dynamic-tool": + return "hammer"; + case "reasoning": + return "brain"; + case "agent-tool": + return "bot"; + case "tone-tool": + return "zap"; + case "update": + case "mixed": + return "hammer"; + } +} + +export function workToneIcon(tone: TimelineWorkEntry["tone"]): { + iconName: WorkEntryIconName; + className: string; +} { + if (tone === "error") { + return { + iconName: "circle-alert", + className: "text-foreground/92", + }; + } + if (tone === "thinking") { + return { + iconName: "brain", + className: "text-icon-muted", + }; + } + if (tone === "info") { + return { + iconName: "check", + className: "text-muted-foreground", + }; + } + return { + iconName: "zap", + className: "text-foreground/92", + }; +} + +export function workEntryRawCommand( + workEntry: Pick, +): string | null { + const rawCommand = workEntry.rawCommand?.trim(); + if (!rawCommand || !workEntry.command) { + return null; + } + return rawCommand === workEntry.command.trim() ? null : rawCommand; +} + +export function buildToolCallExpandedBody( + workEntry: TimelineWorkEntry, + workspaceRoot: string | undefined, + visibleLabel: string, + viewedImagePath: string | null, +): string | null { + const blocks: string[] = []; + const seen = new Set([visibleLabel.trim()]); + const addBlock = (value: string | null | undefined) => { + const text = value?.trim(); + if (!text || seen.has(text)) return; + seen.add(text); + blocks.push(text); + }; + if (workEntry.itemType === "dynamic_tool" && workEntry.toolData !== undefined) { + const input = + workEntry.structuredPayload?.type === "dynamic_tool" + ? workEntry.structuredPayload.input + : workEntry.toolData !== null && + typeof workEntry.toolData === "object" && + "input" in workEntry.toolData + ? workEntry.toolData.input + : undefined; + if (input !== undefined) addBlock(`Tool input\n${JSON.stringify(input, null, 2)}`); + } + const command = workEntry.command?.trim(); + const raw = workEntryRawCommand(workEntry); + if (command === visibleLabel.trim()) { + seen.add(command); + } else { + addBlock(raw ?? command); + } + const detail = workEntry.detail?.trim(); + if (detail !== viewedImagePath?.trim()) { + addBlock(detail); + } + const viewedImagePaths = new Set( + viewedImagePath + ? [viewedImagePath.trim(), formatWorkspaceRelativePath(viewedImagePath, workspaceRoot)] + : [], + ); + const changedFiles = (workEntry.changedFiles ?? []).flatMap((filePath) => { + const formattedPath = formatWorkspaceRelativePath(filePath, workspaceRoot); + return viewedImagePaths.has(filePath) || + viewedImagePaths.has(formattedPath) || + filePath.trim() === detail || + formattedPath === detail + ? [] + : [formattedPath]; + }); + if (changedFiles.length > 0) { + addBlock([...new Set(changedFiles)].join("\n")); + } + return blocks.length > 0 ? blocks.join("\n\n") : null; +} + +export function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName { + if (workEntry.structuredPayload?.type === "notification") { + if (workEntry.structuredPayload.outcome === "failed") return "circle-alert"; + const source = workEntry.structuredPayload.source; + switch (source.kind) { + case "subagent": + case "delegated_task": + return "bot"; + case "command": + return "terminal"; + case "monitor": + return "eye"; + case "background_task": + return "zap"; + case "system": + return "t3-code"; + default: + source satisfies never; + return "zap"; + } + } + if (workEntry.itemType === "user_input_request" || workEntry.itemType === "approval_request") { + return "message-circle"; + } + if (workEntry.toolSurface) return workEntry.toolSurface; + if ( + workEntry.structuredPayload?.type === "dynamic_tool" && + isJavaScriptReplTool(workEntry.structuredPayload.toolName) + ) + return "code"; + const toolPresentation = resolveWorkEntryToolPresentation(workEntry); + if (toolPresentation) return toolPresentation.icon; + const action = toolGroupAction(workEntry); + if (action !== "other") return toolGroupSummaryIconName(action); + + switch (workEntry.itemType) { + case "dynamic_tool": + return "wrench"; + case "subagent": + return "bot"; + } + + return workToneIcon(workEntry.tone).iconName; +} + +/** Compact reasoning keeps formulas while flattening other Markdown into label text. */ +export function remarkThoughtPreview(fallback: string) { + return (tree: Root) => { + const children: RootContent[] = []; + const append = (value: string) => { + const last = children.at(-1); + const text = value.replace(/\s+/g, " "); + if (last?.type === "text") last.value = (last.value + text).replace(/\s+/g, " "); + else children.push({ type: "text", value: text }); + }; + const visit = (node: Root | RootContent) => { + if (node.type === "html" || node.type === "definition") return; + if (node.type === "math" || node.type === "inlineMath") { + children.push({ + type: "inlineMath", + value: node.value, + position: node.position, + data: { + hName: "code", + hProperties: { className: ["language-math", "math-inline"] }, + hChildren: [{ type: "text", value: node.value }], + }, + }); + } else if ("alt" in node) append(node.alt ?? ""); + else if ("value" in node) append(node.value); + else if ("children" in node) { + node.children.forEach((child, index) => { + if ( + index && + ["root", "blockquote", "list", "listItem", "table", "tableRow"].includes(node.type) + ) + append(" "); + visit(child); + }); + } else if (node.type === "break") append(" "); + }; + visit(tree); + const first = children[0]; + const last = children.at(-1); + if (first?.type === "text") first.value = first.value.trimStart(); + if (last?.type === "text") last.value = last.value.trimEnd(); + tree.children = children.some((child) => child.type !== "text" || child.value) + ? children + : [{ type: "text", value: fallback }]; + }; +} diff --git a/apps/web/src/components/chat/TimelineWorkEntryRow.tsx b/apps/web/src/components/chat/TimelineWorkEntryRow.tsx new file mode 100644 index 0000000000000..cf72597f43eca --- /dev/null +++ b/apps/web/src/components/chat/TimelineWorkEntryRow.tsx @@ -0,0 +1,617 @@ +import { WorkLogDetails, WorkLogRow } from "./WorkLog"; +import { + getQuestionAnswerPreview, + getQuestionAnswerText, + getQuestionTextPreview, + hasQuestionAnswer, +} from "@t3tools/client-runtime/work-log/user-input"; +import { + ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE, + type EnvironmentId, + ThreadId, +} from "@t3tools/contracts"; +import { scopeThreadRef } from "@t3tools/client-runtime/environment"; +import { Link } from "@tanstack/react-router"; +import { notificationChildThreadId } from "@t3tools/client-runtime/state/thread-execution"; +import { + resolveViewedImageAsset, + workEntryViewedImagePath, +} from "@t3tools/client-runtime/work-log/presentation"; +import { + turnItemHasDetail, + turnItemNeedsDetailFetch, +} from "@t3tools/client-runtime/work-log/item-detail"; +import { claudeSkillInvocation } from "@t3tools/shared/toolActivity"; +import { memo, use, useState, type KeyboardEvent } from "react"; +import { + workEntryDisplayIndicatesToolFailure, + workEntrySignalsSevereFailure, + workLogEntryIsToolLike, +} from "../../session-logic"; +import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown"; +import { ChevronRightIcon, CircleAlertIcon, XIcon, RotateCcwIcon } from "lucide-react"; +import { Button, InlineButton } from "../ui/button"; +import { useAssetUrls } from "../../assets/assetUrls"; +import { useThreadShell } from "../../state/entities"; +import { + threadReadLabelPrefix, + threadReadTargetId, + threadReadTargetTitle, + toolGroupAction, + workEntryDisplayLabel, + workEntryReadOutput, +} from "./MessagesTimeline.logic"; +import { cn } from "~/lib/utils"; +import { formatUpcomingTimestamp } from "../../timestampFormat"; +import { FetchedToolOutput, V2ItemInspector } from "./V2ItemInspector"; +import { SubagentNotificationLink } from "./V2LifecycleRow"; +import { + TimelineRowCtx, + TimelineRowActivityCtx, + WorkGroupViewCtx, + type TimelineWorkEntry, +} from "./timelineRowContext"; +import { MESSAGE_HEADING_LEVEL, TimelineRowTimestamp } from "./TimelineRowParts"; +import { + failedToolIconClassName, + toolIconAcceptsTint, + ToolActivityIconView, +} from "./TimelineToolActivityIcon"; +import { + workToneIcon, + workEntryRawCommand, + buildToolCallExpandedBody, + workEntryIconName, + remarkThoughtPreview, +} from "./TimelineWorkEntryRow.logic"; + +export function useThreadReadTarget(entry: TimelineWorkEntry, environmentId: EnvironmentId) { + const rawThreadId = threadReadTargetId(entry); + const threadId = rawThreadId === null ? null : ThreadId.make(rawThreadId); + const shell = useThreadShell(threadId ? scopeThreadRef(environmentId, threadId) : null); + const title = threadReadTargetTitle(shell); + return threadId && title ? { threadId, title } : null; +} + +export function threadReadLabel(label: string, target: ReturnType) { + const prefix = target && threadReadLabelPrefix(label); + return prefix ? { ...target, prefix, text: `${prefix} “${target.title}”` } : null; +} + +export function ThreadReadLabel({ + label, + environmentId, + linked, +}: { + label: NonNullable>; + environmentId: EnvironmentId; + linked: boolean; +}) { + return ( + + {label.prefix} “ + {linked ? ( + + {label.title} + + ) : ( + {label.title} + )} + ” + + ); +} + +const toolCallExpandedBodyClassName = + "max-h-64 cursor-text overflow-auto whitespace-pre-wrap break-words font-mono text-secondary-label text-(length:--font-size-code,var(--text-2xs)) leading-relaxed select-text"; + +const stopRowToggle = (e: { stopPropagation: () => void }) => e.stopPropagation(); + +export function ReasoningTraceContent({ entries }: { entries: ReadonlyArray }) { + const ctx = use(TimelineRowCtx); + const { isWorking, latestRunId } = use(TimelineRowActivityCtx); + const isStreaming = (entry: TimelineWorkEntry) => + isWorking && entry.runId === latestRunId && entry.toolLifecycleStatus === "inProgress"; + return ( + + {entries.map((entry) => ( + + ))} + + ); +} + +type WorkEntryRowProps = { + workEntry: TimelineWorkEntry; + workspaceRoot: string | undefined; + displayLabel?: string | undefined; + onToggleEntry?: ((collapsed: boolean) => void) | undefined; +}; + +export const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: WorkEntryRowProps) { + const ctx = use(TimelineRowCtx); + const item = props.workEntry.projectedItem?.item; + const childThreadId = + item?.type === "notification" ? notificationChildThreadId(item.source) : undefined; + if (item?.type !== "notification" || childThreadId === undefined) { + return ; + } + return ( + } + /> + ); +}); + +function WorkEntryLogRow(props: WorkEntryRowProps) { + const { workEntry, workspaceRoot, displayLabel } = props; + const ctx = use(TimelineRowCtx); + const { threadRef, onImageExpand, timestampFormat } = ctx; + const { retryableWorkspacePreparationRunIds, onRetryWorkspacePreparation } = ctx; + const threadTarget = useThreadReadTarget(workEntry, ctx.activeThreadEnvironmentId); + const createdThread = + workEntry.projectedItem?.item.type === "thread_created" + ? workEntry.projectedItem.item + : undefined; + const notifiedSubagentThreadId = + workEntry.projectedItem?.item.type === "notification" + ? notificationChildThreadId(workEntry.projectedItem.item.source) + : undefined; + const groupView = use(WorkGroupViewCtx); + const [expanded, setExpanded] = useState( + () => groupView?.state.expandedEntries.has(workEntry.id) ?? false, + ); + const toggleExpanded = () => { + const next = !expanded; + if (groupView) { + groupView.onToggleEntry(!next); + if (next) groupView.state.expandedEntries.add(workEntry.id); + else groupView.state.expandedEntries.delete(workEntry.id); + } else { + props.onToggleEntry?.(!next); + } + setExpanded(next); + }; + const failureItem = workEntry.projectedItem?.item; + if (failureItem?.type === "error" && failureItem.status === "failed") { + const warning = failureItem.failure.class === "usage_limit"; + const resetAt = failureItem.failure.resetAt; + const resetTime = resetAt ? formatUpcomingTimestamp(resetAt, timestampFormat) : null; + const label = warning + ? `Usage limit reached.${resetTime ? ` Retry after ${resetTime}.` : ""}` + : workEntry.label; + const retryRunId = + failureItem.runId !== null && + retryableWorkspacePreparationRunIds.has(failureItem.runId) && + failureItem.failure.code === ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE + ? failureItem.runId + : null; + return ( + + } + label={ + + {label} + + } + trailing={ + + } + > + {!warning ? ( +

+ {failureItem.failure.message} +

+ ) : null} + {retryRunId !== null && onRetryWorkspacePreparation ? ( +
+ +
+ ) : null} +
+ ); + } + const iconConfig = workToneIcon(workEntry.tone); + const showWarningIndicator = workEntry.sourceActivityKind === "runtime.warning"; + const showFailedIndicator = + !showWarningIndicator && workEntryDisplayIndicatesToolFailure(workEntry); + const showDestructiveRowStyle = + !showWarningIndicator && + showFailedIndicator && + (workEntrySignalsSevereFailure(workEntry) || !workLogEntryIsToolLike(workEntry)); + const entryIconName = + showWarningIndicator || showDestructiveRowStyle ? "circle-alert" : workEntryIconName(workEntry); + const entryToolIcon = + showWarningIndicator || showDestructiveRowStyle + ? undefined + : (workEntry.toolIcon ?? workEntry.toolSource?.icon); + const isReasoning = workEntry.itemType === "reasoning"; + const questionHeading = workEntry.questionAnswer + ? getQuestionTextPreview(workEntry.questionAnswer) + : ""; + const previewText = + isReasoning && expanded + ? workEntry.toolLifecycleStatus === "inProgress" + ? "Thinking" + : "Thought" + : questionHeading || (displayLabel ?? workEntryDisplayLabel(workEntry, workspaceRoot)); + const answerPreview = + workEntry.questionAnswer && hasQuestionAnswer(workEntry.questionAnswer) + ? getQuestionAnswerPreview(workEntry.questionAnswer) + : null; + const viewedImagePath = workEntryViewedImagePath(workEntry); + const payload = workEntry.structuredPayload; + const skill = + payload?.type === "dynamic_tool" + ? claudeSkillInvocation(payload.toolName, payload.input) + : undefined; + const plainOutput = + toolGroupAction(workEntry) === "read" + ? workEntryReadOutput(workEntry, workspaceRoot) + : skill + ? (skill.args ?? null) + : undefined; + const viewedImage = + viewedImagePath && threadRef + ? resolveViewedImageAsset(viewedImagePath, { + threadId: threadRef.threadId, + workspaceRoot, + }) + : null; + const canExpand = + previewText.trim().length > 0 || + (workEntry.itemType === "dynamic_tool" && workEntry.toolData !== undefined) || + Boolean( + workEntryRawCommand(workEntry) || + workEntry.command?.trim() || + workEntry.detail?.trim() || + workEntry.changedFiles?.length || + viewedImage, + ); + const expandedBody = + expanded && !isReasoning + ? plainOutput !== undefined + ? plainOutput + : buildToolCallExpandedBody( + workEntry, + workspaceRoot, + previewText, + viewedImage ? viewedImagePath : null, + ) + : null; + const plainOutputFetches = + plainOutput !== undefined && + workEntry.projectedItem !== undefined && + turnItemNeedsDetailFetch(workEntry.projectedItem.item); + const canExpandProjectedItem = + plainOutput !== undefined + ? Boolean(plainOutput || viewedImage || workEntry.questionAnswer || plainOutputFetches) + : workEntry.projectedItem === undefined + ? canExpand + : isReasoning + ? Boolean(workEntry.detail?.trim()) + : Boolean( + viewedImage || + workEntry.questionAnswer || + turnItemHasDetail(workEntry.projectedItem.item), + ); + const iconWrapperClass = cn( + "flex size-4 items-center justify-center", + showWarningIndicator + ? "text-warning" + : showDestructiveRowStyle + ? "text-destructive" + : showFailedIndicator + ? failedToolIconClassName + : workEntry.tone === "tool" + ? "text-icon-muted" + : iconConfig.className, + ); + const headingClass = showWarningIndicator + ? "font-medium text-warning" + : showDestructiveRowStyle + ? "font-medium text-destructive" + : workLogEntryIsToolLike(workEntry) + ? "text-secondary-label" + : "text-foreground/80"; + const threadLabel = isReasoning ? null : threadReadLabel(previewText, threadTarget); + const accessiblePreview = [threadLabel?.text ?? previewText, answerPreview] + .filter(Boolean) + .join(": "); + const accessibleDisplayText = showFailedIndicator + ? `${accessiblePreview}, tool call failed` + : accessiblePreview; + const rowToggleProps = canExpandProjectedItem + ? { + role: "button" as const, + tabIndex: 0 as const, + "aria-label": accessibleDisplayText, + "aria-expanded": expanded, + onClick: toggleExpanded, + onKeyDown: (e: KeyboardEvent) => { + if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + toggleExpanded(); + } + }, + } + : {}; + + return ( + + + + } + label={ +
+

+ + {isReasoning && !expanded ? ( + + ) : threadLabel ? ( + + ) : ( + previewText + )} + + {answerPreview ? ( + + {answerPreview} + + ) : null} +

+
+ } + trailing={ + <> + {createdThread ? ( + + ) : null} + {notifiedSubagentThreadId ? ( + { + event.stopPropagation(); + ctx.onOpenThread(notifiedSubagentThreadId); + }} + onKeyDown={stopRowToggle} + > + Open subagent + + ) : null} + {showFailedIndicator && + !showDestructiveRowStyle && + !toolIconAcceptsTint(entryToolIcon) ? ( + + ) : null} + + + + + + } + > + {expanded && viewedImage && threadRef ? ( + + + + ) : null} + {expanded && workEntry.questionAnswer ? ( + + ) : null} + {expanded && isReasoning ? : null} + {expanded && + !isReasoning && + !workEntry.questionAnswer && + canExpandProjectedItem && + (expandedBody || + plainOutputFetches || + (workEntry.projectedItem && plainOutput === undefined)) ? ( + + {workEntry.projectedItem && plainOutput === undefined ? ( + + ) : ( + <> + {expandedBody ? ( +
{expandedBody}
+ ) : null} + {plainOutputFetches && workEntry.projectedItem ? ( + + ) : null} + + )} +
+ ) : null} +
+ ); +} + +function QuestionAnswerHistory({ + answer, +}: { + answer: import("@t3tools/contracts").UserInputAttachmentAnswerPayload; +}) { + const { activeThreadEnvironmentId } = use(TimelineRowCtx); + const attachments = Object.values(answer.attachmentsByQuestionId).flat(); + const resources = attachments.map((attachment) => ({ + _tag: "attachment" as const, + attachmentId: attachment.id, + })); + const urls = useAssetUrls(activeThreadEnvironmentId, resources); + return ( +
+ {[ + ...new Set([ + ...Object.keys(answer.questionTextById ?? {}), + ...Object.keys(answer.answers), + ...Object.keys(answer.attachmentsByQuestionId), + ]), + ].map((questionId) => ( +
+ {answer.questionTextById?.[questionId] ? ( +

+ {answer.questionTextById[questionId]} +

+ ) : null} + {getQuestionAnswerText(answer.answers[questionId]) ? ( +

+ {getQuestionAnswerText(answer.answers[questionId])} +

+ ) : null} +
+ {(answer.attachmentsByQuestionId[questionId] ?? []).map((attachment) => { + const url = urls[attachments.indexOf(attachment)]; + return ( + + {attachment.type === "image" && url ? ( + {attachment.name} + ) : ( + attachment.name + )} + + ); + })} +
+
+ ))} +
+ ); +} diff --git a/apps/web/src/components/chat/timelineRowContext.ts b/apps/web/src/components/chat/timelineRowContext.ts new file mode 100644 index 0000000000000..fdfb833ecc6aa --- /dev/null +++ b/apps/web/src/components/chat/timelineRowContext.ts @@ -0,0 +1,99 @@ +import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; +import { + type EnvironmentId, + type MessageId, + type OrchestrationV2TurnItem, + type RunAttemptId, + type ScopedThreadRef, + type ServerProviderSkill, + type RunId, + ThreadId, +} from "@t3tools/contracts"; +import type { WorkflowOpening } from "@t3tools/client-runtime/state/subagentRuntime"; +import { createContext, type MouseEvent } from "react"; +import { type LegendListRef } from "@legendapp/list/react"; +import type { CodexArtifactTemplate } from "@t3tools/shared/codexArtifactTemplates"; +import { type ChatFileAttachment } from "../../types"; +import { ExpandedImagePreview } from "./ExpandedImagePreview"; +import { type AssistantCitationTarget } from "./AssistantCitationSource"; +import { type MessagesTimelineRow, type WorkGroupScrollAnchor } from "./MessagesTimeline.logic"; +import { type TimestampFormat } from "@t3tools/contracts/settings"; +import { type HandoffTimelineRun } from "./V2LifecycleRow"; + +export interface TimelineRowSharedState { + citationRequest: AssistantCitationTarget | null; + listRef: React.RefObject; + timestampFormat: TimestampFormat; + routeThreadKey: string; + threadRef: ScopedThreadRef | null; + markdownCwd: string | undefined; + resolvedTheme: "light" | "dark"; + workspaceRoot: string | undefined; + skills: ReadonlyArray>; + runs: ReadonlyArray; + activeThreadEnvironmentId: EnvironmentId; + onRevertToTurnCount: (targetTurnCount: number, messageId: MessageId) => void; + onUseArtifactTemplate: (template: CodexArtifactTemplate) => void; + onSendAppMessage: ((text: string) => Promise) | undefined; + onAppFullscreenChange: (rowId: string, fullscreen: boolean) => void; + onRunShellCommand: ((command: string) => void) | undefined; + findActive: boolean; + onImageExpand: (preview: ExpandedImagePreview) => void; + displayThreadKey?: string; + onOpenTurnDiff: (runId: RunId, filePath?: string) => void; + onOpenThread: (threadId: OrchestrationV2TurnItem["threadId"]) => void; + workflowOpening: WorkflowOpening | null; + onForkFromRun: (input: { + readonly sourceThreadId: ThreadId; + readonly runId: RunId; + }) => Promise; + onRollbackCheckpoint: (input: { + readonly checkpointId: string; + readonly scopeId: string; + }) => void; + onToggleTurnFold: (runId: RunId) => void; + onToggleAttemptFold: (attemptId: RunAttemptId) => void; + onFileOpen: (attachment: ChatFileAttachment) => void; + onFileDownload: (attachment: ChatFileAttachment) => void; + openPullRequest: (event: MouseEvent, url: string) => void; + onToggleWorkGroup: (groupId: string, anchorKey: string) => void; + onToggleWorkEntry: (anchorKey: string, collapsed: boolean) => void; + onCancelWorktreeSetup: (() => void) | null; + retryableWorkspacePreparationRunIds: ReadonlySet; + onRetryWorkspacePreparation: ((runId: RunId) => void) | null; + onWorktreeSetupWorkLocally: (() => void) | null; + onOpenWorktreeSetupTerminal: ((terminalId: string) => void) | null; + workGroupViewState: WorkGroupViewState; +} + +export interface TimelineRowActivityState { + isWorking: boolean; + isCompacting: boolean; + isRevertingCheckpoint: boolean; + activeTurnInProgress: boolean; + awaitingUser: boolean; + isPreparingWorktree: boolean; + latestRunId: RunId | null; + backgroundWorktreeSetup: WorktreeSetupSnapshot | null; +} + +export const TimelineRowCtx = createContext(null!); + +export const TimelineRowActivityCtx = createContext(null!); + +export interface WorkGroupViewState { + scrollPositions: Map; + expandedEntries: Set; +} + +export const WorkGroupViewCtx = createContext<{ + state: WorkGroupViewState; + onToggleEntry: (collapsed: boolean) => void; +} | null>(null); + +export type TimelineWorkEntry = Extract< + MessagesTimelineRow, + { kind: "work" } +>["groupedEntries"][number]; + +export type TimelineRow = MessagesTimelineRow; From 9e672d0974640171c8576468d2db93d2f2975a58 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:49 -0700 Subject: [PATCH 010/179] refactor(web): timeline work group and live activity rows live in their own file (#22675) Co-authored-by: Claude Opus 5.5 --- .../src/components/chat/MessagesTimeline.tsx | 640 +----------------- .../chat/TimelineWorkGroupRows.logic.ts | 20 + .../components/chat/TimelineWorkGroupRows.tsx | 640 ++++++++++++++++++ 3 files changed, 667 insertions(+), 633 deletions(-) create mode 100644 apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts create mode 100644 apps/web/src/components/chat/TimelineWorkGroupRows.tsx diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 2bcd94805f266..956b82e3f7162 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -4,16 +4,11 @@ import { shallow } from "zustand/vanilla/shallow"; import { shouldPreserveAssistantLineBreaks } from "@t3tools/shared/markdownPipeline"; import { MarkdownFindContext, findRevealRef } from "./markdownFindContext"; import { useChatCanvas } from "./ChatCanvasContext"; -import { WorkLogBlock, WorkLogButton, WorkLogList, WorkLogRow } from "./WorkLog"; +import { WorkLogBlock } from "./WorkLog"; import { PullRequestGlyph } from "../pullRequest/pullRequestIcons"; import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; import { ReadOnlySourcePreview } from "../files/AttachmentFilePreview"; import { useRightPanelStore } from "~/rightPanelStore"; -import { - getQuestionAnswerPreview, - getQuestionTextPreview, - hasQuestionAnswer, -} from "@t3tools/client-runtime/work-log/user-input"; import { deriveTimelineMinimapItems } from "./timelineMinimapItems"; import { COMPOSER_CONTEXT_KINDS, @@ -25,7 +20,6 @@ import { type ServerProviderSkill, type RunId, ThreadId, - type ToolActivityIcon, } from "@t3tools/contracts"; import { parseScopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime/environment"; import { useAtomValue } from "@effect/atom-react"; @@ -34,7 +28,6 @@ import { resolveUserMessagePresentation } from "@t3tools/client-runtime/user-mes import { Link } from "@tanstack/react-router"; import { canForkProjectedAssistantItem } from "@t3tools/client-runtime/state/thread-workflows"; import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; -import { liveThoughtLine } from "@t3tools/client-runtime/work-log/presentation"; import { resolveWorkGroupScrollAnchor } from "@t3tools/client-runtime/work-log/scroll-anchor"; import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments"; import { @@ -49,7 +42,6 @@ const NOOP_USE_ARTIFACT_TEMPLATE = () => {}; const NOOP_OPEN_ATTACHMENT = (_attachment: ChatFileAttachment) => {}; import { resolveChatListAnchoredEndSpace } from "@t3tools/shared/chatList"; -import { formatDuration } from "@t3tools/shared/orchestrationTiming"; import { observeVisibleAnimation } from "../../lib/visibleAnimation"; import { memo, @@ -73,7 +65,6 @@ import { providerErrorPresentation, createMessageAttachmentPreviewProjector, selectMessageImageResources, - workEntryDisplayIndicatesToolFailure, } from "../../session-logic"; import type { CodexArtifactTemplate } from "@t3tools/shared/codexArtifactTemplates"; import { @@ -125,7 +116,6 @@ import { MorphIcon } from "~/components/MorphIcon"; import { useAssetUrlRefresh, useAssetUrls, useAssetUrlState } from "../../assets/assetUrls"; import { AttachmentImage } from "../media/AttachmentImage"; import { MediaVideoPlayer } from "../media/MediaVideoPlayer"; -import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; import { buildAttachmentVideoAsset, buildAttachmentVideoPreview, @@ -161,7 +151,6 @@ import { computeStableMessagesTimelineRows, deriveMessagesTimelineRowsWithState, type MessagesTimelineRowsProjection, - liveWorkEntryLabel, resolveAssistantMessageCopyState, resolveTimelineIsAtEnd, resolveTimelineMinimapHasPersistentGutter, @@ -169,11 +158,8 @@ import { isTimelineMinimapRowVisible, resolveTimelineMinimapVisibleRange, resolveTimelineMinimapHitStripWidth, - resolveWorkGroupScrollIndex, shouldCollapseUserMessage, - shouldFollowWorkGroupAppend, timelineTurnFoldRunIdsByEntryId, - workEntryIsVisibleInGroup, worktreeSetupAgentStarted, type StableMessagesTimelineRowsState, type MessagesTimelineRow, @@ -184,8 +170,6 @@ import { useThreadFindNavigation } from "./useThreadFindNavigation"; import { readThreadFindPosition } from "./threadFindPosition"; import type { ThreadFindPositionReader } from "./threadFind"; import { TerminalContextInlineChip } from "./TerminalContextInlineChip"; -import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; -import { Spinner } from "../ui/spinner"; import { Tooltip, TooltipPopup, TooltipTrigger, TooltipScrollDismissArea } from "../ui/tooltip"; import { WorktreeSetupCard } from "./WorktreeSetupCard"; import { @@ -256,8 +240,6 @@ import { TimelineRowCtx, TimelineRowActivityCtx, type WorkGroupViewState, - WorkGroupViewCtx, - type TimelineWorkEntry, type TimelineRow, } from "./timelineRowContext"; import { @@ -267,23 +249,12 @@ import { ActivityShimmerOverlay, } from "./TimelineRowParts"; import { - failedToolIconClassName, - toolIconAcceptsTint, - type WorkEntryIconName, - ToolActivityIconView, -} from "./TimelineToolActivityIcon"; -import { - useThreadReadTarget, - threadReadLabel, - ThreadReadLabel, - ReasoningTraceContent, - SimpleWorkEntryRow, -} from "./TimelineWorkEntryRow"; -import { - remarkThoughtPreview, - toolGroupSummaryIconName, - workEntryIconName, -} from "./TimelineWorkEntryRow.logic"; + WorkGroupSection, + WorkingTimelineRow, + ThinkingTimelineRow, + LiveWorkEntryTimelineRow, + WorkGroupToggleTimelineRow, +} from "./TimelineWorkGroupRows"; function timelineListStateFor(identity: string): { readonly identity: string; @@ -2871,603 +2842,6 @@ const V2SubagentGroup = memo(function V2SubagentGroup({ ); }); -const WorkGroupSection = memo(function WorkGroupSection({ - anchorKey, - disclosureAnchorKey = anchorKey, - groupedEntries, - isExpandedToolGroup, - displayLabel, -}: { - anchorKey: string; - disclosureAnchorKey?: string; - groupedEntries: Extract["groupedEntries"]; - isExpandedToolGroup: boolean; - displayLabel?: string | undefined; -}) { - const { workspaceRoot, routeThreadKey, onToggleWorkEntry } = use(TimelineRowCtx); - const onToggleStandaloneEntry = (collapsed: boolean) => - onToggleWorkEntry(disclosureAnchorKey, collapsed); - const nonEmptyEntries = groupedEntries.filter((entry) => - workEntryIsVisibleInGroup(entry, isExpandedToolGroup), - ); - - if (nonEmptyEntries.length === 0) return null; - if (isExpandedToolGroup && nonEmptyEntries.every((entry) => entry.itemType === "reasoning")) { - return ; - } - if (isExpandedToolGroup) { - return ( - - ); - } - - return ( -
- {nonEmptyEntries.map((workEntry) => ( - - ))} -
- ); -}); - -function formatWorkingTimer(startIso: string, endIso: string): string | null { - const startedAtMs = Date.parse(startIso); - const endedAtMs = Date.parse(endIso); - if (!Number.isFinite(startedAtMs) || !Number.isFinite(endedAtMs)) { - return null; - } - - const elapsedSeconds = Math.max(0, Math.floor((endedAtMs - startedAtMs) / 1000)); - if (elapsedSeconds < 60) { - return `${elapsedSeconds}s`; - } - - return formatDuration(elapsedSeconds * 1_000); -} - -function formatWorkingTimerNow(startIso: string): string { - return formatWorkingTimer(startIso, new Date().toISOString()) ?? "0s"; -} - -function WorkingTimer({ createdAt }: { createdAt: string }) { - const textRef = useRef(null); - const initialText = formatWorkingTimerNow(createdAt); - - useEffect(() => { - const updateText = () => { - if (textRef.current) { - textRef.current.textContent = formatWorkingTimerNow(createdAt); - } - }; - updateText(); - const id = setInterval(updateText, 1000); - return () => clearInterval(id); - }, [createdAt]); - - return ( - - {initialText} - - ); -} - -const compactWorkEntryHeight = 24; - -function ExpandedWorkGroupEntries({ - anchorKey, - disclosureAnchorKey, - entries, - workspaceRoot, -}: { - anchorKey: string; - disclosureAnchorKey: string; - entries: TimelineWorkEntry[]; - workspaceRoot: string | undefined; -}) { - const { workGroupViewState: viewState, onToggleWorkEntry } = use(TimelineRowCtx); - const [initialScrollIndex] = useState(() => - resolveWorkGroupScrollIndex(entries, viewState.scrollPositions.get(anchorKey)), - ); - const [restoringPosition, setRestoringPosition] = useState(initialScrollIndex !== undefined); - const listRef = useRef(null); - const [expandedContentHeight, setExpandedContentHeight] = useState(0); - const [fades, setFades] = useState({ top: false, bottom: false, viewportHeight: 0 }); - const [appendState, setAppendState] = useState({ entries, follow: false }); - // Capture the pre-change edge once per incoming array, before new layout - // metrics arrive. Edge/viewport changes never turn a status update into a follow. - if (appendState.entries !== entries) { - setAppendState({ - entries, - follow: - fades.viewportHeight > 0 && - shouldFollowWorkGroupAppend(appendState.entries, entries, fades.bottom ? Infinity : 0), - }); - } - - const groupView = { - state: viewState, - onToggleEntry: (collapsed: boolean) => onToggleWorkEntry(disclosureAnchorKey, collapsed), - }; - const updateScrollFades = () => { - const element = listRef.current?.getScrollableNode(); - if (!element) return; - const distanceFromEnd = element.scrollHeight - element.clientHeight - element.scrollTop; - const viewportHeight = element.clientHeight; - const top = element.scrollTop > 1; - const bottom = distanceFromEnd > 1; - setFades((previous) => - previous.top === top && - previous.bottom === bottom && - previous.viewportHeight === viewportHeight - ? previous - : { top, bottom, viewportHeight }, - ); - }; - - const handleScroll = () => { - const state = listRef.current?.getState(); - const position = state && resolveWorkGroupScrollAnchor(state); - if (position) { - viewState.scrollPositions.set(anchorKey, { - entryId: position.rowId, - offset: position.offsetWithinRow, - }); - } - updateScrollFades(); - }; - - const handleLoad = () => { - const list = listRef.current; - const element = list?.getScrollableNode(); - if (initialScrollIndex && list && element) { - // Bootstrap can report the restored target before the DOM has applied it. - // Reconcile once at load, before releasing the measured anchor row. - const offset = Math.max( - 0, - Math.min(list.getState().scroll, element.scrollHeight - element.clientHeight), - ); - if (Math.abs(element.scrollTop - offset) > 1) { - void list.scrollToOffset({ offset, animated: false }); - } - } - setRestoringPosition(false); - }; - - useLayoutEffect(() => { - const element = listRef.current?.getScrollableNode(); - if (!element) return; - updateScrollFades(); - const content = element.firstElementChild; - return observeResize(content ? [element, content] : element, updateScrollFades); - }, [updateScrollFades]); - - const renderEntry = ({ item }: { item: TimelineWorkEntry }) => ( - - ); - - const updateExpandedContentHeight = () => { - const state = listRef.current?.getState(); - let height = 0; - for (const entryId of viewState.expandedEntries) { - if (state?.indexByKey(entryId) === undefined) continue; - height += Math.max( - 0, - (state.sizes.get(entryId) ?? compactWorkEntryHeight) - compactWorkEntryHeight, - ); - } - setExpandedContentHeight(height); - }; - - useLayoutEffect(updateExpandedContentHeight, [entries, updateExpandedContentHeight]); - - return ( - - - - - - ); -} - -const workEntryKey = (entry: TimelineWorkEntry) => entry.id; - -function WorkingTimelineRow({ row }: { row: Extract }) { - const { isCompacting, isPreparingWorktree, backgroundWorktreeSetup } = - use(TimelineRowActivityCtx); - const shimmer = isPreparingWorktree || isCompacting; - const label = isPreparingWorktree ? ( - "Setting up worktree…" - ) : isCompacting ? ( - - ) : row.createdAt ? ( - <> - Working for - - ) : ( - "Working..." - ); - return ( -
-
- - {label} - {shimmer ? {label} : null} - - {backgroundWorktreeSetup ? ( - - ) : null} -
-
- ); -} - -function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnapshot }) { - const ctx = use(TimelineRowCtx); - const terminalId = snapshot.setupScript?.terminalId ?? null; - const openTerminal = ctx.onOpenWorktreeSetupTerminal; - const onOpenTerminal = openTerminal && terminalId ? () => openTerminal(terminalId) : null; - const scriptName = snapshot.setupScript?.name ?? "Setup script"; - return ( - - - } - > - - {scriptName} - - - - - - ); -} - -function CompactingLabel() { - return ( - - - ); -} - -function ThinkingTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const { isCompacting, isPreparingWorktree } = use(TimelineRowActivityCtx); - if (isPreparingWorktree || isCompacting) return ; - const activity = ; - const { groupId } = row; - if (groupId === undefined) return activity; - return ( - - ); -} - -function LiveActivityRow({ - label, - iconName, - toolIcon, - failed = false, - active = false, - shimmer = false, -}: { - label: ReactNode; - iconName?: WorkEntryIconName; - toolIcon?: ToolActivityIcon | undefined; - failed?: boolean; - active?: boolean; - shimmer?: boolean; -}) { - const animated = active && !failed; - const showShimmer = animated && shimmer; - return ( -
- - {showShimmer ? ( - - - - ) : null} -
- ); -} - -function LiveActivityContent({ - label, - iconName, - toolIcon, - failed = false, - announceFailure = false, - active = false, - highlighted = false, -}: { - label: ReactNode; - iconName: WorkEntryIconName | undefined; - toolIcon?: ToolActivityIcon | undefined; - failed?: boolean; - announceFailure?: boolean; - active?: boolean; - highlighted?: boolean; -}) { - const showTrailingFailureMark = - failed && iconName !== undefined && !toolIconAcceptsTint(toolIcon); - - return ( - - - - ) : null - } - label={ - - {label} - - } - trailing={ - showTrailingFailureMark ? ( - - ) : null - } - /> - ); -} - -function LiveWorkEntryTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const threadTarget = useThreadReadTarget(row.entry, ctx.activeThreadEnvironmentId); - const questionHeading = row.entry.questionAnswer - ? getQuestionTextPreview(row.entry.questionAnswer) - : ""; - const label = questionHeading || liveWorkEntryLabel(row.entry, ctx.workspaceRoot, row.active); - const threadLabel = threadReadLabel(label, threadTarget); - - const failed = workEntryDisplayIndicatesToolFailure(row.entry); - const thoughtLine = row.thought && !row.expanded ? liveThoughtLine(row.thought.detail ?? "") : ""; - const thoughtIsStatus = thoughtLine !== "" && row.entry === row.thought; - - return ( - - ); -} - -function WorkGroupToggleTimelineRow({ - row, -}: { - row: Extract; -}) { - const ctx = use(TimelineRowCtx); - return ( - ctx.onToggleWorkGroup(row.groupId, row.id)} - /> - ); -} - -function WorkGroupHeader(props: { - label: string; - iconName: WorkEntryIconName; - toolIcon?: ToolActivityIcon | undefined; - failed?: boolean | undefined; - active?: boolean | undefined; - expanded: boolean; - createdAt: string; - timestampFormat: TimestampFormat; - onToggle: () => void; -}) { - return ( - - } - label={ - - {props.label} - - } - trailing={ - - } - /> - ); -} - /** Subscribes directly to the UI state store for expand/collapse state, * so toggling re-renders only this component — not the entire list. */ const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({ diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts b/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts new file mode 100644 index 0000000000000..e4a8fe4a583c9 --- /dev/null +++ b/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts @@ -0,0 +1,20 @@ +import { formatDuration } from "@t3tools/shared/orchestrationTiming"; + +function formatWorkingTimer(startIso: string, endIso: string): string | null { + const startedAtMs = Date.parse(startIso); + const endedAtMs = Date.parse(endIso); + if (!Number.isFinite(startedAtMs) || !Number.isFinite(endedAtMs)) { + return null; + } + + const elapsedSeconds = Math.max(0, Math.floor((endedAtMs - startedAtMs) / 1000)); + if (elapsedSeconds < 60) { + return `${elapsedSeconds}s`; + } + + return formatDuration(elapsedSeconds * 1_000); +} + +export function formatWorkingTimerNow(startIso: string): string { + return formatWorkingTimer(startIso, new Date().toISOString()) ?? "0s"; +} diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx new file mode 100644 index 0000000000000..11b56a19038c7 --- /dev/null +++ b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx @@ -0,0 +1,640 @@ +import { WorkLogButton, WorkLogList, WorkLogRow } from "./WorkLog"; +import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; +import { + getQuestionAnswerPreview, + getQuestionTextPreview, + hasQuestionAnswer, +} from "@t3tools/client-runtime/work-log/user-input"; +import { type ToolActivityIcon } from "@t3tools/contracts"; +import { liveThoughtLine } from "@t3tools/client-runtime/work-log/presentation"; +import { resolveWorkGroupScrollAnchor } from "@t3tools/client-runtime/work-log/scroll-anchor"; +import { observeResize } from "~/lib/observeResize"; +import { observeVisibleAnimation } from "../../lib/visibleAnimation"; +import { memo, use, useEffect, useLayoutEffect, useRef, useState, type ReactNode } from "react"; +import { LegendList, type LegendListRef } from "@legendapp/list/react"; +import { workEntryDisplayIndicatesToolFailure } from "../../session-logic"; +import ChatMarkdown from "../ChatMarkdown"; +import { Minimize2Icon, XIcon } from "lucide-react"; +import { Button } from "../ui/button"; +import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; +import { + liveWorkEntryLabel, + resolveWorkGroupScrollIndex, + shouldFollowWorkGroupAppend, + workEntryIsVisibleInGroup, + type MessagesTimelineRow, +} from "./MessagesTimeline.logic"; +import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; +import { Spinner } from "../ui/spinner"; +import { WorktreeSetupCard } from "./WorktreeSetupCard"; +import { cn } from "~/lib/utils"; +import { type TimestampFormat } from "@t3tools/contracts/settings"; +import { + TimelineRowCtx, + TimelineRowActivityCtx, + WorkGroupViewCtx, + type TimelineWorkEntry, + type TimelineRow, +} from "./timelineRowContext"; +import { TimelineRowTimestamp, ActivityShimmerOverlay } from "./TimelineRowParts"; +import { + failedToolIconClassName, + toolIconAcceptsTint, + type WorkEntryIconName, + ToolActivityIconView, +} from "./TimelineToolActivityIcon"; +import { + useThreadReadTarget, + threadReadLabel, + ThreadReadLabel, + ReasoningTraceContent, + SimpleWorkEntryRow, +} from "./TimelineWorkEntryRow"; +import { + remarkThoughtPreview, + toolGroupSummaryIconName, + workEntryIconName, +} from "./TimelineWorkEntryRow.logic"; +import { formatWorkingTimerNow } from "./TimelineWorkGroupRows.logic"; + +export const WorkGroupSection = memo(function WorkGroupSection({ + anchorKey, + disclosureAnchorKey = anchorKey, + groupedEntries, + isExpandedToolGroup, + displayLabel, +}: { + anchorKey: string; + disclosureAnchorKey?: string; + groupedEntries: Extract["groupedEntries"]; + isExpandedToolGroup: boolean; + displayLabel?: string | undefined; +}) { + const { workspaceRoot, routeThreadKey, onToggleWorkEntry } = use(TimelineRowCtx); + const onToggleStandaloneEntry = (collapsed: boolean) => + onToggleWorkEntry(disclosureAnchorKey, collapsed); + const nonEmptyEntries = groupedEntries.filter((entry) => + workEntryIsVisibleInGroup(entry, isExpandedToolGroup), + ); + + if (nonEmptyEntries.length === 0) return null; + if (isExpandedToolGroup && nonEmptyEntries.every((entry) => entry.itemType === "reasoning")) { + return ; + } + if (isExpandedToolGroup) { + return ( + + ); + } + + return ( +
+ {nonEmptyEntries.map((workEntry) => ( + + ))} +
+ ); +}); + +function WorkingTimer({ createdAt }: { createdAt: string }) { + const textRef = useRef(null); + const initialText = formatWorkingTimerNow(createdAt); + + useEffect(() => { + const updateText = () => { + if (textRef.current) { + textRef.current.textContent = formatWorkingTimerNow(createdAt); + } + }; + updateText(); + const id = setInterval(updateText, 1000); + return () => clearInterval(id); + }, [createdAt]); + + return ( + + {initialText} + + ); +} + +const compactWorkEntryHeight = 24; + +function ExpandedWorkGroupEntries({ + anchorKey, + disclosureAnchorKey, + entries, + workspaceRoot, +}: { + anchorKey: string; + disclosureAnchorKey: string; + entries: TimelineWorkEntry[]; + workspaceRoot: string | undefined; +}) { + const { workGroupViewState: viewState, onToggleWorkEntry } = use(TimelineRowCtx); + const [initialScrollIndex] = useState(() => + resolveWorkGroupScrollIndex(entries, viewState.scrollPositions.get(anchorKey)), + ); + const [restoringPosition, setRestoringPosition] = useState(initialScrollIndex !== undefined); + const listRef = useRef(null); + const [expandedContentHeight, setExpandedContentHeight] = useState(0); + const [fades, setFades] = useState({ top: false, bottom: false, viewportHeight: 0 }); + const [appendState, setAppendState] = useState({ entries, follow: false }); + // Capture the pre-change edge once per incoming array, before new layout + // metrics arrive. Edge/viewport changes never turn a status update into a follow. + if (appendState.entries !== entries) { + setAppendState({ + entries, + follow: + fades.viewportHeight > 0 && + shouldFollowWorkGroupAppend(appendState.entries, entries, fades.bottom ? Infinity : 0), + }); + } + + const groupView = { + state: viewState, + onToggleEntry: (collapsed: boolean) => onToggleWorkEntry(disclosureAnchorKey, collapsed), + }; + const updateScrollFades = () => { + const element = listRef.current?.getScrollableNode(); + if (!element) return; + const distanceFromEnd = element.scrollHeight - element.clientHeight - element.scrollTop; + const viewportHeight = element.clientHeight; + const top = element.scrollTop > 1; + const bottom = distanceFromEnd > 1; + setFades((previous) => + previous.top === top && + previous.bottom === bottom && + previous.viewportHeight === viewportHeight + ? previous + : { top, bottom, viewportHeight }, + ); + }; + + const handleScroll = () => { + const state = listRef.current?.getState(); + const position = state && resolveWorkGroupScrollAnchor(state); + if (position) { + viewState.scrollPositions.set(anchorKey, { + entryId: position.rowId, + offset: position.offsetWithinRow, + }); + } + updateScrollFades(); + }; + + const handleLoad = () => { + const list = listRef.current; + const element = list?.getScrollableNode(); + if (initialScrollIndex && list && element) { + // Bootstrap can report the restored target before the DOM has applied it. + // Reconcile once at load, before releasing the measured anchor row. + const offset = Math.max( + 0, + Math.min(list.getState().scroll, element.scrollHeight - element.clientHeight), + ); + if (Math.abs(element.scrollTop - offset) > 1) { + void list.scrollToOffset({ offset, animated: false }); + } + } + setRestoringPosition(false); + }; + + useLayoutEffect(() => { + const element = listRef.current?.getScrollableNode(); + if (!element) return; + updateScrollFades(); + const content = element.firstElementChild; + return observeResize(content ? [element, content] : element, updateScrollFades); + }, [updateScrollFades]); + + const renderEntry = ({ item }: { item: TimelineWorkEntry }) => ( + + ); + + const updateExpandedContentHeight = () => { + const state = listRef.current?.getState(); + let height = 0; + for (const entryId of viewState.expandedEntries) { + if (state?.indexByKey(entryId) === undefined) continue; + height += Math.max( + 0, + (state.sizes.get(entryId) ?? compactWorkEntryHeight) - compactWorkEntryHeight, + ); + } + setExpandedContentHeight(height); + }; + + useLayoutEffect(updateExpandedContentHeight, [entries, updateExpandedContentHeight]); + + return ( + + + + + + ); +} + +const workEntryKey = (entry: TimelineWorkEntry) => entry.id; + +export function WorkingTimelineRow({ row }: { row: Extract }) { + const { isCompacting, isPreparingWorktree, backgroundWorktreeSetup } = + use(TimelineRowActivityCtx); + const shimmer = isPreparingWorktree || isCompacting; + const label = isPreparingWorktree ? ( + "Setting up worktree…" + ) : isCompacting ? ( + + ) : row.createdAt ? ( + <> + Working for + + ) : ( + "Working..." + ); + return ( +
+
+ + {label} + {shimmer ? {label} : null} + + {backgroundWorktreeSetup ? ( + + ) : null} +
+
+ ); +} + +function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnapshot }) { + const ctx = use(TimelineRowCtx); + const terminalId = snapshot.setupScript?.terminalId ?? null; + const openTerminal = ctx.onOpenWorktreeSetupTerminal; + const onOpenTerminal = openTerminal && terminalId ? () => openTerminal(terminalId) : null; + const scriptName = snapshot.setupScript?.name ?? "Setup script"; + return ( + + + } + > + + {scriptName} + + + + + + ); +} + +function CompactingLabel() { + return ( + + + ); +} + +export function ThinkingTimelineRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + const { isCompacting, isPreparingWorktree } = use(TimelineRowActivityCtx); + if (isPreparingWorktree || isCompacting) return ; + const activity = ; + const { groupId } = row; + if (groupId === undefined) return activity; + return ( + + ); +} + +function LiveActivityRow({ + label, + iconName, + toolIcon, + failed = false, + active = false, + shimmer = false, +}: { + label: ReactNode; + iconName?: WorkEntryIconName; + toolIcon?: ToolActivityIcon | undefined; + failed?: boolean; + active?: boolean; + shimmer?: boolean; +}) { + const animated = active && !failed; + const showShimmer = animated && shimmer; + return ( +
+ + {showShimmer ? ( + + + + ) : null} +
+ ); +} + +function LiveActivityContent({ + label, + iconName, + toolIcon, + failed = false, + announceFailure = false, + active = false, + highlighted = false, +}: { + label: ReactNode; + iconName: WorkEntryIconName | undefined; + toolIcon?: ToolActivityIcon | undefined; + failed?: boolean; + announceFailure?: boolean; + active?: boolean; + highlighted?: boolean; +}) { + const showTrailingFailureMark = + failed && iconName !== undefined && !toolIconAcceptsTint(toolIcon); + + return ( + + + + ) : null + } + label={ + + {label} + + } + trailing={ + showTrailingFailureMark ? ( + + ) : null + } + /> + ); +} + +export function LiveWorkEntryTimelineRow({ + row, +}: { + row: Extract; +}) { + const ctx = use(TimelineRowCtx); + const threadTarget = useThreadReadTarget(row.entry, ctx.activeThreadEnvironmentId); + const questionHeading = row.entry.questionAnswer + ? getQuestionTextPreview(row.entry.questionAnswer) + : ""; + const label = questionHeading || liveWorkEntryLabel(row.entry, ctx.workspaceRoot, row.active); + const threadLabel = threadReadLabel(label, threadTarget); + + const failed = workEntryDisplayIndicatesToolFailure(row.entry); + const thoughtLine = row.thought && !row.expanded ? liveThoughtLine(row.thought.detail ?? "") : ""; + const thoughtIsStatus = thoughtLine !== "" && row.entry === row.thought; + + return ( + + ); +} + +export function WorkGroupToggleTimelineRow({ + row, +}: { + row: Extract; +}) { + const ctx = use(TimelineRowCtx); + return ( + ctx.onToggleWorkGroup(row.groupId, row.id)} + /> + ); +} + +function WorkGroupHeader(props: { + label: string; + iconName: WorkEntryIconName; + toolIcon?: ToolActivityIcon | undefined; + failed?: boolean | undefined; + active?: boolean | undefined; + expanded: boolean; + createdAt: string; + timestampFormat: TimestampFormat; + onToggle: () => void; +}) { + return ( + + } + label={ + + {props.label} + + } + trailing={ + + } + /> + ); +} From 3e36c5e1817734cec615e9ee88801a81208861d0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:50 -0700 Subject: [PATCH 011/179] refactor(web): timeline user message rows and context chips live in their own files (#22676) Co-authored-by: Claude Opus 5.5 --- .../src/components/chat/MessagesTimeline.tsx | 1231 +---------------- .../chat/TimelineUserMessageRow.logic.ts | 25 + .../chat/TimelineUserMessageRow.tsx | 619 +++++++++ .../chat/UserMessageContextChips.tsx | 592 ++++++++ 4 files changed, 1243 insertions(+), 1224 deletions(-) create mode 100644 apps/web/src/components/chat/TimelineUserMessageRow.logic.ts create mode 100644 apps/web/src/components/chat/TimelineUserMessageRow.tsx create mode 100644 apps/web/src/components/chat/UserMessageContextChips.tsx diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 956b82e3f7162..c707e369eb442 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -1,17 +1,12 @@ -import { useRenderedAppearance } from "../../hooks/useTheme"; import { ThreadFindTimelineContext } from "./ThreadFindProvider"; import { shallow } from "zustand/vanilla/shallow"; import { shouldPreserveAssistantLineBreaks } from "@t3tools/shared/markdownPipeline"; -import { MarkdownFindContext, findRevealRef } from "./markdownFindContext"; +import { MarkdownFindContext } from "./markdownFindContext"; import { useChatCanvas } from "./ChatCanvasContext"; import { WorkLogBlock } from "./WorkLog"; -import { PullRequestGlyph } from "../pullRequest/pullRequestIcons"; import type { WorktreeSetupSnapshot } from "@t3tools/contracts"; -import { ReadOnlySourcePreview } from "../files/AttachmentFilePreview"; -import { useRightPanelStore } from "~/rightPanelStore"; import { deriveTimelineMinimapItems } from "./timelineMinimapItems"; import { - COMPOSER_CONTEXT_KINDS, type AssistantCitation, type EnvironmentId, type MessageId, @@ -24,12 +19,8 @@ import { import { parseScopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime/environment"; import { useAtomValue } from "@effect/atom-react"; import { environmentThreadDetails } from "../../state/threads"; -import { resolveUserMessagePresentation } from "@t3tools/client-runtime/user-message"; -import { Link } from "@tanstack/react-router"; import { canForkProjectedAssistantItem } from "@t3tools/client-runtime/state/thread-workflows"; -import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; import { resolveWorkGroupScrollAnchor } from "@t3tools/client-runtime/work-log/scroll-anchor"; -import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments"; import { subagentGroupSummary, summarizeSubagentStatuses, @@ -58,74 +49,28 @@ import { type LegendListRef, type MaintainScrollAtEndOptions, } from "@legendapp/list/react"; -import { FileDiff } from "@pierre/diffs/react"; -import { DiffWorkerPoolProvider } from "../DiffWorkerPoolProvider"; -import { - type TimelineEntry, - providerErrorPresentation, - createMessageAttachmentPreviewProjector, - selectMessageImageResources, -} from "../../session-logic"; +import { type TimelineEntry, providerErrorPresentation } from "../../session-logic"; import type { CodexArtifactTemplate } from "@t3tools/shared/codexArtifactTemplates"; -import { - type ChatMessage, - type ChatFileAttachment, - type ChatImageAttachment, - isFileAttachment, - isImageAttachment, - isVideoAttachment, - type TurnDiffSummary, -} from "../../types"; -import { - getRenderablePatch, - resolveDiffThemeName, - resolveFileDiffPath, -} from "../../lib/diffRendering"; -import { PREFERRED_HIGHLIGHTER } from "../../lib/syntaxHighlighting"; +import { type ChatMessage, type ChatFileAttachment, type TurnDiffSummary } from "../../types"; import ChatMarkdown from "../ChatMarkdown"; -import { ThreadContextChip } from "../ThreadContextChip"; import { ArrowRightIcon, BotIcon, CheckIcon, ChevronDownIcon, CircleAlertIcon, - DownloadIcon, - EyeIcon, GitForkIcon, type LucideIcon, - MessageCircleIcon, - MousePointerClickIcon, - PaintbrushIcon, MinusIcon, - Redo2Icon, Minimize2Icon, - Undo2Icon, WrenchIcon, XIcon, ZapIcon, } from "lucide-react"; -import { ChevronDown, ChevronRight, Link as LinkIconNode } from "lucide"; -import type { - ComposerContextId, - ComposerContextRecord, - KnownComposerContextRecord, -} from "@t3tools/contracts"; -import { Button, InlineButton } from "../ui/button"; +import { ChevronDown, ChevronRight } from "lucide"; +import { Button } from "../ui/button"; import { MorphIcon } from "~/components/MorphIcon"; -import { useAssetUrlRefresh, useAssetUrls, useAssetUrlState } from "../../assets/assetUrls"; -import { AttachmentImage } from "../media/AttachmentImage"; -import { MediaVideoPlayer } from "../media/MediaVideoPlayer"; -import { - buildAttachmentVideoAsset, - buildAttachmentVideoPreview, - buildExpandedImagePreview, - ExpandedImagePreview, -} from "./ExpandedImagePreview"; -import { - SNAP_SHOT_ATTACHMENT_FRAME_CLASS, - SnapShotAttachmentDetails, -} from "./SnapShotAttachmentDetails"; +import { ExpandedImagePreview } from "./ExpandedImagePreview"; import { ProposedPlanCard } from "./ProposedPlanCard"; import { HtmlRenderFrame } from "./HtmlRenderFrame"; import { McpAppFrame } from "./McpAppFrame"; @@ -140,9 +85,6 @@ import { timelineContentOverflowsViewport, } from "./timelineScrollAnchoring"; import { MessageCopyButton } from "./MessageCopyButton"; -import { promptLinkFor } from "../promptLinks/copyPromptLink"; -import { PierreEntryIcon } from "./PierreEntryIcon"; -import { WorkspaceEntryIcon, WorkspaceEntryTooltip } from "./WorkspaceEntryIcon"; import { AssistantSelectionToolbar } from "./AssistantSelectionToolbar"; import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection"; import { AssistantCitationSource, type AssistantCitationRequest } from "./AssistantCitationSource"; @@ -158,7 +100,6 @@ import { isTimelineMinimapRowVisible, resolveTimelineMinimapVisibleRange, resolveTimelineMinimapHitStripWidth, - shouldCollapseUserMessage, timelineTurnFoldRunIdsByEntryId, worktreeSetupAgentStarted, type StableMessagesTimelineRowsState, @@ -169,42 +110,10 @@ import { type ThreadFindMatch } from "./threadFind"; import { useThreadFindNavigation } from "./useThreadFindNavigation"; import { readThreadFindPosition } from "./threadFindPosition"; import type { ThreadFindPositionReader } from "./threadFind"; -import { TerminalContextInlineChip } from "./TerminalContextInlineChip"; import { Tooltip, TooltipPopup, TooltipTrigger, TooltipScrollDismissArea } from "../ui/tooltip"; import { WorktreeSetupCard } from "./WorktreeSetupCard"; -import { - ContextChipPopover as UserMessageContextPopover, - ContextChipShell, - FileChip, - ImageChipButton, - PULL_REQUEST_CHIP_KINDS, - PullRequestChip, - UnresolvedChip, -} from "../contextChipParts"; -import { - asKnownContextRecord, - isPullRequestSummaryContext, - pullRequestContextDisplayState, - pullRequestContextKindLabel, - resolveUserMessageContext, - reviewCommentContextLabel, - selectedMessageContextFragment, -} from "~/lib/composerContextRecords"; -import { - collectComposerContextReferences, - formatComposerContextReference, -} from "@t3tools/shared/composerContextReferences"; -import { - COMPOSER_CONTEXT_CLIPBOARD_MIME, - encodeComposerContextClipboardHtml, - encodeComposerContextFragment, -} from "@t3tools/shared/composerContextClipboard"; -import { chatMarkdownClipboardPayload } from "../../markdown-clipboard"; -import { ContextChip, ContextChipLabel, type ContextChipKind } from "../ContextChip"; -import { createContextPresentationRegistry } from "../contextPresentationRegistry"; import { useOpenPrLink } from "~/lib/openPullRequestLink"; import { useClientSettings } from "~/hooks/useSettings"; -import type { ChatMarkdownContextReference } from "../ChatMarkdown"; import { useMediaQuery } from "~/hooks/useMediaQuery"; import { cn } from "~/lib/utils"; import { useUiStateStore } from "~/uiStateStore"; @@ -222,17 +131,9 @@ import { } from "./V2LifecycleRow"; import { SecretRequestCard } from "./SecretRequestCard"; import { TimelineSystemDivider } from "./TimelineSystemDivider"; -import { WorkflowOpeningBlock } from "./WorkflowOpeningBlock"; -import { SkillChipIcon } from "./SkillInlineText"; import * as DateTime from "effect/DateTime"; -import { formatWorkspaceRelativePath } from "../../filePathDisplay"; import { forwardWheelToTimeline } from "./timelineMinimapWheel"; -import { - buildReviewCommentRenderablePatch, - formatReviewCommentFence, - type ReviewCommentContext, -} from "../../reviewCommentContext"; import { TimelineMinimap } from "./TimelineMinimap"; import { type TimelineRowSharedState, @@ -255,6 +156,7 @@ import { LiveWorkEntryTimelineRow, WorkGroupToggleTimelineRow, } from "./TimelineWorkGroupRows"; +import { UserMessageRow } from "./TimelineUserMessageRow"; function timelineListStateFor(identity: string): { readonly identity: string; @@ -1650,487 +1552,6 @@ function ContextCompactionTimelineRow({ ); } -function UserVideoAttachment({ file }: { readonly file: ChatFileAttachment }) { - const ctx = use(TimelineRowCtx); - // Row updates rebuild attachment objects, so the asset follows only the - // fields it reads. - const { downloadable, id, mimeType, name } = file; - const environmentId = ctx.activeThreadEnvironmentId; - const asset = - downloadable === false - ? null - : buildAttachmentVideoAsset(environmentId, { id, mimeType, name }); - const resource = asset?.resource ?? null; - const assetUrl = useAssetUrlState(ctx.activeThreadEnvironmentId, resource); - const refreshAssetUrl = useAssetUrlRefresh(ctx.activeThreadEnvironmentId, resource); - const src = assetUrl._tag === "Success" ? assetUrl.url : (file.previewUrl ?? null); - - if (asset === null && src === null) { - return ( -
- {file.name} -
- ); - } - - return ( - { - const preview = buildAttachmentVideoPreview(ctx.activeThreadEnvironmentId, file); - if (preview) ctx.onImageExpand(preview); - }} - className="block aspect-[4/3] w-full" - videoClassName="aspect-auto size-full rounded-lg border border-border/80" - stateClassName="aspect-auto min-h-full rounded-lg border border-border/80 bg-black text-white" - onRetry={asset ? refreshAssetUrl : undefined} - actionsSource={asset ? { kind: "video", name: file.name, src, asset } : undefined} - /> - ); -} - -function UserMessageRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - return ctx.workflowOpening?.messageId === row.message.id ? ( - ctx.onOpenThread(ThreadId.make(threadId))} - /> - ) : ( - - ); -} - -function UserTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const { onImageExpand, onFileOpen } = ctx; - const senderThreadId = row.message.senderThreadId; - const resources = selectMessageImageResources(row.message.attachments); - const previewUrls = useAssetUrls(ctx.activeThreadEnvironmentId, resources); - const [projectPreviews] = useState(createMessageAttachmentPreviewProjector); - const previewUrlsById = new Map( - resources.flatMap((resource, index) => { - const url = previewUrls[index]; - return url ? [[resource.attachmentId, url] as const] : []; - }), - ); - const messageWithPreviews = projectPreviews(row.message, (attachment) => - previewUrlsById.get(attachment.id), - ); - const userImages = (messageWithPreviews.attachments ?? []).filter(isImageAttachment); - const userFiles = (row.message.attachments ?? []).filter(isFileAttachment); - const userVideos = userFiles.filter(isVideoAttachment); - const otherUserFiles = userFiles.filter((file) => !isVideoAttachment(file)); - const unknownAttachments = (row.message.attachments ?? []).filter( - (attachment) => !isImageAttachment(attachment) && !isFileAttachment(attachment), - ); - const userMessage = resolveUserMessagePresentation(row.message); - const resolvedContext = resolveUserMessageContext(row.message); - const previewImages = userImages.filter((image) => image.name.startsWith("preview-annotation-")); - const revertTurnCount = row.revertTurnCount; - const chippedAttachmentIds = new Set( - collectComposerContextReferences(resolvedContext.text).flatMap((occurrence) => { - const record = asKnownContextRecord(resolvedContext.recordsById.get(occurrence.contextId)); - return record?.kind === "file" || record?.kind === "image" ? [record.attachmentId] : []; - }), - ); - const regularImages = userImages.filter((image) => !image.name.startsWith("preview-annotation-")); - const unchippedFiles = otherUserFiles.filter((file) => !chippedAttachmentIds.has(file.id)); - const annotationRecordIds = resolvedContext.records - .filter((record) => record.kind === "preview-annotation") - .map((record) => record.contextId); - const plainText = replaceComposerContextReferences( - resolvedContext.text, - (reference) => reference.label, - ); - const promptLink = promptLinkFor(plainText); - const contextClipboardFragment = - resolvedContext.records.length === 0 - ? null - : encodeComposerContextFragment({ - version: 1, - source: { - environmentId: ctx.activeThreadEnvironmentId, - ...(ctx.threadRef ? { threadId: ctx.threadRef.threadId } : {}), - messageId: row.message.id, - }, - records: resolvedContext.records, - }); - const onBodyCopyCapture = (event: React.ClipboardEvent) => { - if (resolvedContext.records.length === 0 || !event.clipboardData) return; - const selection = window.getSelection(); - if (!selection || selection.isCollapsed) return; - const copiedMarkdown: string[] = []; - for (let index = 0; index < selection.rangeCount; index += 1) { - const container = document.createElement("div"); - container.appendChild(selection.getRangeAt(index).cloneContents()); - // Diagram source is copied text, not authority to export context records. - for (const element of container.querySelectorAll( - "[data-markdown-copy]:not([data-markdown-mermaid])", - )) { - copiedMarkdown.push(element.getAttribute("data-markdown-copy") ?? ""); - } - } - const fragment = selectedMessageContextFragment({ - markdown: copiedMarkdown.join("\n"), - records: resolvedContext.records, - environmentId: ctx.activeThreadEnvironmentId, - ...(ctx.threadRef ? { threadId: ctx.threadRef.threadId } : {}), - messageId: row.message.id, - }); - if (!fragment) return; - // Claim the copy: without preventDefault the browser default overwrites the - // custom MIME type. The default content must then be written back explicitly. - const payload = chatMarkdownClipboardPayload(selection); - event.preventDefault(); - event.clipboardData.setData("text/plain", payload?.text ?? selection.toString()); - if (payload) { - event.clipboardData.setData( - "text/html", - encodeComposerContextClipboardHtml(payload.text, fragment, payload.html), - ); - } - event.clipboardData.setData(COMPOSER_CONTEXT_CLIPBOARD_MIME, fragment); - }; - const renderContextReference = (reference: ChatMarkdownContextReference) => { - const record = asKnownContextRecord(resolvedContext.recordsById.get(reference.contextId)); - const annotationImage = - record?.kind === "preview-annotation" - ? resolvePreviewAnnotationImage({ - record, - recordsById: resolvedContext.recordsById, - userImages, - previewImages, - annotationRecordIds, - }) - : null; - const attachment = - record?.kind === "image" - ? (userImages.find((image) => image.id === record.attachmentId) ?? null) - : record?.kind === "file" - ? (userFiles.find((file) => file.id === record.attachmentId) ?? null) - : null; - return ( - - { - const preview = buildExpandedImagePreview(userImages, image.id); - if (preview) onImageExpand(preview); - }} - onOpenFile={onFileOpen} - onExpandVideo={(file) => { - const preview = buildAttachmentVideoPreview(ctx.activeThreadEnvironmentId, file); - if (preview) onImageExpand(preview); - }} - /> - - ); - }; - - return ( -
- {userMessage.attribution === "automation" ? ( -

- {userMessage.scheduledTaskId ? ( - - Sent by automation - - ) : ( - "Sent by automation" - )} -

- ) : userMessage.attribution === "t3code" ? ( -

- Sent by T3 Code -

- ) : userMessage.attribution === "agent" ? ( -

- {senderThreadId ? ( - ctx.onOpenThread(senderThreadId)} - tone="muted" - aria-label="Open sending thread" - > - Sent by another agent - - ) : ( - "Sent by another agent" - )} -

- ) : null} - {row.message.inputIntent === "steer" || - row.message.inputIntent === "promoted_queued_to_steer" ? ( - - ) : null} -
- You - {(regularImages.length > 0 || userVideos.length > 0) && ( -
- {regularImages.map((image) => ( -
- {image.previewUrl ? ( - - ) : ( -
- {image.name} -
- )} - {image.previewUrl && image.source?.kind === "snap-shot" ? ( - - ) : null} -
- ))} - {userVideos.map((file) => ( - - ))} -
- )} - {unchippedFiles.length > 0 || unknownAttachments.length > 0 ? ( -
- {unchippedFiles.map((file) => { - const fileIdentity = ( - <> - - {file.name} - - ); - if (file.downloadable !== false) { - return ( -
- - - ctx.onFileDownload(file)} - /> - } - > - - - Download {file.name} - -
- ); - } - - return ( -
- {fileIdentity} -
- ); - })} - {unknownAttachments.map((attachment) => ( -
- - {attachment.name} -
- ))} -
- ) : null} -
- -
-
- {row.projectedItem && - row.projectedItem.item.status !== "completed" && - row.projectedItem.item.status !== "pending" && - row.projectedItem.item.status !== "waiting" ? ( -
- - {row.projectedItem.item.status} - -
- ) : null} -
-
- - }> - {formatDayAwareTimestamp(row.message.createdAt, ctx.timestampFormat)} - - - {formatChatTimestampTooltip(row.message.createdAt, ctx.timestampFormat)} - - -
- {typeof revertTurnCount === "number" && ( - - )} - {resolvedContext.text && ( - - )} - {promptLink !== null && ( - - )} -
-
-
-
- ); -} - -function UserMessageIntentMarker({ - intent, -}: { - readonly intent: "steer" | "promoted_queued_to_steer"; -}) { - return ( - - - } - > - - - {intent === "promoted_queued_to_steer" - ? "Originally queued, then promoted to steer the active turn" - : "Steered the active turn"} - - - ); -} - -function resolvePreviewAnnotationImage(input: { - record: Extract; - recordsById: ReadonlyMap; - userImages: ReadonlyArray; - previewImages: ReadonlyArray; - annotationRecordIds: ReadonlyArray; -}): ChatImageAttachment | null { - const screenshotRecord = input.record.screenshotContextId - ? asKnownContextRecord(input.recordsById.get(input.record.screenshotContextId)) - : undefined; - return ( - (screenshotRecord?.kind === "image" - ? input.userImages.find((image) => image.id === screenshotRecord.attachmentId) - : undefined) ?? - input.previewImages.find( - (image) => image.name === `preview-annotation-${input.record.annotationId}.png`, - ) ?? - input.previewImages[input.annotationRecordIds.indexOf(input.record.contextId)] ?? - null - ); -} - -function RevertUserMessageButton({ - turnCount, - messageId, -}: { - turnCount: number; - messageId: MessageId; -}) { - const ctx = use(TimelineRowCtx); - const activity = use(TimelineRowActivityCtx); - - return ( - - ctx.onRevertToTurnCount(turnCount, messageId)} - aria-label="Edit from here" - /> - } - > - - - Edit from here - - ); -} - function TurnFoldTimelineRow({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); @@ -2928,644 +2349,6 @@ function AssistantChangedFilesSectionInner({ ); } -function UserMessageMentionChip(props: { - record: Extract; - copyMarkdown: string; -}) { - const ctx = use(TimelineRowCtx); - return ( - - } - aria-label={`Preview ${props.record.path}`} - data-markdown-copy={props.copyMarkdown} - onClick={() => { - if (ctx.threadRef) - useRightPanelStore.getState().openFile(ctx.threadRef, props.record.path); - }} - > - - {props.record.label} - - } - /> - - - - - ); -} - -function UserMessageContextChip(props: { - icon: ReactNode; - label: string; - kindLabel?: string; - copyMarkdown: string; - tooltip?: string; - kind: ContextChipKind; -}) { - return ( - - ); -} - -function UserMessagePullRequestContextChip(props: { - record: Extract; - copyMarkdown: string; - kind: ContextChipKind; -}) { - const { activeThreadEnvironmentId, openPullRequest } = use(TimelineRowCtx); - const metadata = props.record.pullRequest; - if (metadata === undefined) return null; - return ( - - ); -} - -function UserMessagePreviewAnnotationDetails(props: { - record: Extract; - image: ChatImageAttachment | null; -}) { - const ctx = use(TimelineRowCtx); - const visibleElements = props.record.elements ?? []; - return ( -
- {props.image?.previewUrl ? ( - - ) : ( -
- Screenshot unavailable -
- )} -
-
- {props.record.pageTitle?.trim() || props.record.pageUrl || "Preview annotation"} -
- {props.record.comment ? ( -
- {props.record.comment} -
- ) : null} -
- {props.record.targetSummary ? ( - {props.record.targetSummary} - ) : null} - {(props.record.styleChanges?.length ?? 0) > 0 ? ( - - - {props.record.styleChanges?.length ?? 0} - - ) : null} -
- {visibleElements.length > 0 ? ( -
- {visibleElements.map((element) => { - const source = element.source; - const sourceLabel = source?.fileName - ? `${source.fileName}${source.lineNumber === null ? "" : `:${source.lineNumber}`}` - : null; - return ( -
-
- - {element.selector || `<${element.tagName}>`} - - {sourceLabel ? ( - {sourceLabel} - ) : null} -
- {element.htmlPreview?.trim() ? ( -
-                      {element.htmlPreview.trim()}
-                    
- ) : null} -
- ); - })} - {(props.record.elements?.length ?? 0) > visibleElements.length ? ( -
- {(props.record.elements?.length ?? 0) - visibleElements.length} more selected - elements -
- ) : null} -
- ) : null} -
-
- ); -} - -function UserMessageElementDetails({ - record, -}: { - record: Extract; -}) { - const sourceLabel = record.source?.fileName - ? `${record.source.fileName}${record.source.lineNumber === null ? "" : `:${record.source.lineNumber}`}` - : null; - return ( -
-
-
- {record.pageTitle?.trim() || record.pageUrl} -
-
{record.pageUrl}
-
-
-
- - {record.selector || `<${record.tagName}>`} - - {sourceLabel ? ( - {sourceLabel} - ) : null} -
- {record.htmlPreview?.trim() ? ( -
- -
- ) : null} - {record.styles?.trim() ? ( -
- -
- ) : null} -
-
- ); -} - -interface UserMessageContextRenderContext { - reference: ChatMarkdownContextReference; - annotationImage: ChatImageAttachment | null; - attachment: ChatImageAttachment | ChatFileAttachment | null; - resolvedTheme: "light" | "dark"; - copyMarkdown: string; - onExpandImage: (image: ChatImageAttachment) => void; - onExpandVideo: (file: ChatFileAttachment) => void; - onOpenFile: (file: ChatFileAttachment) => void; -} - -function UnavailableUserMessageContextChip(props: UserMessageContextRenderContext) { - return ( - - ); -} - -const userMessageContextPresentationRegistry = createContextPresentationRegistry< - KnownComposerContextRecord, - UserMessageContextRenderContext, - ReactNode ->({ - requiredKinds: COMPOSER_CONTEXT_KINDS, - handlers: [ - { - kind: "mention", - canRender: (record) => record.kind === "mention", - render: (record, context) => - record.kind === "mention" ? ( - - ) : ( - - ), - }, - { - kind: "skill", - canRender: (record) => record.kind === "skill", - render: (record, context) => - record.kind === "skill" ? ( - } - label={record.label || record.name} - kindLabel="Skill" - tooltip={`$${record.name}`} - copyMarkdown={context.copyMarkdown} - kind="skill" - /> - ) : ( - - ), - }, - { - kind: "thread", - canRender: (record) => record.kind === "thread", - render: (record, context) => - record.kind === "thread" ? ( - - ) : ( - - ), - }, - { - kind: "image", - canRender: (record, context) => - record.kind === "image" && - context.attachment !== null && - isImageAttachment(context.attachment), - render: (record, context) => { - if ( - record.kind !== "image" || - context.attachment === null || - !isImageAttachment(context.attachment) - ) { - return ; - } - const attachment = context.attachment; - return ( - context.onExpandImage(attachment)} - /> - ); - }, - }, - { - kind: "file", - canRender: (record, context) => - record.kind === "file" && context.attachment !== null && context.attachment.type === "file", - render: (record, context) => { - if ( - record.kind !== "file" || - context.attachment === null || - context.attachment.type !== "file" - ) { - return ; - } - const attachment = context.attachment; - const isVideo = isVideoAttachment(attachment); - const disabled = - attachment.downloadable === false && (!isVideo || attachment.previewUrl === undefined); - const size = formatAttachmentSize(record.sizeBytes); - return ( - - isVideo ? context.onExpandVideo(attachment) : context.onOpenFile(attachment) - } - tooltip={`${record.name}\n${size}`} - /> - ); - }, - }, - { - kind: "terminal", - canRender: (record) => record.kind === "terminal", - render: (record, context, definition) => - record.kind === "terminal" ? ( - - - - ) : ( - - ), - }, - { - kind: "element", - canRender: (record) => record.kind === "element", - render: (record, context) => - record.kind === "element" ? ( - } - label={record.label} - > - - - ) : ( - - ), - }, - { - kind: "review-comment", - canRender: (record) => record.kind === "review-comment", - render: (record, context) => { - if (record.kind !== "review-comment") { - return ; - } - const isPullRequest = isPullRequestSummaryContext(record); - const label = reviewCommentContextLabel(record); - const kindLabel = isPullRequest ? pullRequestContextKindLabel(record) : "Review comment"; - const pullRequestState = pullRequestContextDisplayState(record) ?? "unknown"; - if (isPullRequest && record.pullRequest !== undefined) { - return ( - - ); - } - return ( - : } - label={label} - > - - - ); - }, - }, - { - kind: "preview-annotation", - canRender: (record) => record.kind === "preview-annotation", - render: (record, context) => - record.kind === "preview-annotation" ? ( - } - label={record.label} - > - - - ) : ( - - ), - }, - ], - fallback: (_kind, _record, context) => , -}); - -function UserMessageContextReferenceChip(props: { - reference: ChatMarkdownContextReference; - record: KnownComposerContextRecord | undefined; - annotationImage: ChatImageAttachment | null; - attachment: ChatImageAttachment | ChatFileAttachment | null; - onExpandImage: (image: ChatImageAttachment) => void; - onExpandVideo: (file: ChatFileAttachment) => void; - onOpenFile: (file: ChatFileAttachment) => void; -}) { - const { resolvedTheme } = use(TimelineRowCtx); - const copyMarkdown = formatComposerContextReference({ - kind: props.reference.kind, - contextId: props.reference.contextId as ComposerContextId, - label: props.reference.label, - }); - return userMessageContextPresentationRegistry.render(props.reference.kind, props.record, { - reference: props.reference, - annotationImage: props.annotationImage, - attachment: props.attachment, - resolvedTheme, - copyMarkdown, - onExpandImage: props.onExpandImage, - onExpandVideo: props.onExpandVideo, - onOpenFile: props.onOpenFile, - }); -} - -const COLLAPSED_USER_MESSAGE_FADE_HEIGHT_REM = 1.75; -const COLLAPSED_USER_MESSAGE_FADE_MASK = `linear-gradient(to bottom, black calc(100% - ${COLLAPSED_USER_MESSAGE_FADE_HEIGHT_REM}rem), transparent)`; - -const CollapsibleUserMessageBody = memo(function CollapsibleUserMessageBody(props: { - text: string; - renderContextReference: (reference: ChatMarkdownContextReference) => ReactNode; - skills: ReadonlyArray>; - markdownCwd: string | undefined; - footer?: ReactNode; -}) { - const [expanded, setExpanded] = useState(false); - const revealForFind = () => setExpanded(true); - const revealRef = findRevealRef(revealForFind); - const hasVisibleBody = props.text.trim().length > 0; - const canCollapse = hasVisibleBody && shouldCollapseUserMessage(props.text); - const isCollapsed = canCollapse && !expanded; - const showCollapseControl = canCollapse; - - return ( -
- {hasVisibleBody ? ( -
- -
- ) : null} - {showCollapseControl || props.footer ? ( -
- {showCollapseControl ? ( - - ) : null} - {props.footer ? ( -
{props.footer}
- ) : null} -
- ) : null} -
- ); -}); - -const UserMessageBody = memo(function UserMessageBody(props: { - text: string; - renderContextReference?: (reference: ChatMarkdownContextReference) => ReactNode; - skills: ReadonlyArray>; - markdownCwd: string | undefined; -}) { - const ctx = use(TimelineRowCtx); - if (props.text.length === 0) { - return null; - } - return ( - - ); -}); - -function UserMessageReviewCommentCard({ comment }: { comment: ReviewCommentContext }) { - const renderedAppearance = useRenderedAppearance(); - const ctx = use(TimelineRowCtx); - const fenceLanguage = comment.fenceLanguage ?? "diff"; - const renderablePatch = getRenderablePatch( - buildReviewCommentRenderablePatch(comment), - `review-comment:${comment.id}`, - ); - - return ( -
-
-
- {formatWorkspaceRelativePath(comment.filePath, ctx.workspaceRoot)} -
-
- {comment.sectionTitle} · {comment.rangeLabel} -
-
- {comment.text.length > 0 && ( -
- -
- )} - {fenceLanguage !== "diff" && comment.diff.trim().length > 0 && ( - - )} - {renderablePatch?.kind === "files" && ( - - {renderablePatch.files.map((fileDiff) => ( - - ))} - - )} - {renderablePatch?.kind === "raw" && ( -
-          {renderablePatch.text}
-        
- )} -
- ); -} - function handoffRunsSignature(runs: ReadonlyArray): string { return runs .map( diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.logic.ts b/apps/web/src/components/chat/TimelineUserMessageRow.logic.ts new file mode 100644 index 0000000000000..9738e29d7ccaa --- /dev/null +++ b/apps/web/src/components/chat/TimelineUserMessageRow.logic.ts @@ -0,0 +1,25 @@ +import { type ChatImageAttachment } from "../../types"; +import type { ComposerContextRecord, KnownComposerContextRecord } from "@t3tools/contracts"; +import { asKnownContextRecord } from "~/lib/composerContextRecords"; + +export function resolvePreviewAnnotationImage(input: { + record: Extract; + recordsById: ReadonlyMap; + userImages: ReadonlyArray; + previewImages: ReadonlyArray; + annotationRecordIds: ReadonlyArray; +}): ChatImageAttachment | null { + const screenshotRecord = input.record.screenshotContextId + ? asKnownContextRecord(input.recordsById.get(input.record.screenshotContextId)) + : undefined; + return ( + (screenshotRecord?.kind === "image" + ? input.userImages.find((image) => image.id === screenshotRecord.attachmentId) + : undefined) ?? + input.previewImages.find( + (image) => image.name === `preview-annotation-${input.record.annotationId}.png`, + ) ?? + input.previewImages[input.annotationRecordIds.indexOf(input.record.contextId)] ?? + null + ); +} diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.tsx b/apps/web/src/components/chat/TimelineUserMessageRow.tsx new file mode 100644 index 0000000000000..388562ef25eae --- /dev/null +++ b/apps/web/src/components/chat/TimelineUserMessageRow.tsx @@ -0,0 +1,619 @@ +import { findRevealRef } from "./markdownFindContext"; +import { type MessageId, type ServerProviderSkill, ThreadId } from "@t3tools/contracts"; +import { resolveUserMessagePresentation } from "@t3tools/client-runtime/user-message"; +import { Link } from "@tanstack/react-router"; +import { replaceComposerContextReferences } from "@t3tools/shared/composerContextReferences"; +import { memo, use, useState, type ReactNode } from "react"; +import { + createMessageAttachmentPreviewProjector, + selectMessageImageResources, +} from "../../session-logic"; +import { + type ChatFileAttachment, + isFileAttachment, + isImageAttachment, + isVideoAttachment, +} from "../../types"; +import ChatMarkdown from "../ChatMarkdown"; +import { DownloadIcon, EyeIcon, Redo2Icon, Undo2Icon } from "lucide-react"; +import { Link as LinkIconNode } from "lucide"; +import { Button, InlineButton } from "../ui/button"; +import { useAssetUrlRefresh, useAssetUrls, useAssetUrlState } from "../../assets/assetUrls"; +import { AttachmentImage } from "../media/AttachmentImage"; +import { MediaVideoPlayer } from "../media/MediaVideoPlayer"; +import { + buildAttachmentVideoAsset, + buildAttachmentVideoPreview, + buildExpandedImagePreview, +} from "./ExpandedImagePreview"; +import { + SNAP_SHOT_ATTACHMENT_FRAME_CLASS, + SnapShotAttachmentDetails, +} from "./SnapShotAttachmentDetails"; +import { MessageCopyButton } from "./MessageCopyButton"; +import { promptLinkFor } from "../promptLinks/copyPromptLink"; +import { PierreEntryIcon } from "./PierreEntryIcon"; +import { shouldCollapseUserMessage } from "./MessagesTimeline.logic"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { + asKnownContextRecord, + resolveUserMessageContext, + selectedMessageContextFragment, +} from "~/lib/composerContextRecords"; +import { collectComposerContextReferences } from "@t3tools/shared/composerContextReferences"; +import { + COMPOSER_CONTEXT_CLIPBOARD_MIME, + encodeComposerContextClipboardHtml, + encodeComposerContextFragment, +} from "@t3tools/shared/composerContextClipboard"; +import { chatMarkdownClipboardPayload } from "../../markdown-clipboard"; +import type { ChatMarkdownContextReference } from "../ChatMarkdown"; +import { cn } from "~/lib/utils"; +import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; +import { WorkflowOpeningBlock } from "./WorkflowOpeningBlock"; +import { TimelineRowCtx, TimelineRowActivityCtx, type TimelineRow } from "./timelineRowContext"; +import { MESSAGE_HEADING_LEVEL, MessageAuthorHeading } from "./TimelineRowParts"; +import { UserMessageContextReferenceChip } from "./UserMessageContextChips"; +import { resolvePreviewAnnotationImage } from "./TimelineUserMessageRow.logic"; + +function UserVideoAttachment({ file }: { readonly file: ChatFileAttachment }) { + const ctx = use(TimelineRowCtx); + // Row updates rebuild attachment objects, so the asset follows only the + // fields it reads. + const { downloadable, id, mimeType, name } = file; + const environmentId = ctx.activeThreadEnvironmentId; + const asset = + downloadable === false + ? null + : buildAttachmentVideoAsset(environmentId, { id, mimeType, name }); + const resource = asset?.resource ?? null; + const assetUrl = useAssetUrlState(ctx.activeThreadEnvironmentId, resource); + const refreshAssetUrl = useAssetUrlRefresh(ctx.activeThreadEnvironmentId, resource); + const src = assetUrl._tag === "Success" ? assetUrl.url : (file.previewUrl ?? null); + + if (asset === null && src === null) { + return ( +
+ {file.name} +
+ ); + } + + return ( + { + const preview = buildAttachmentVideoPreview(ctx.activeThreadEnvironmentId, file); + if (preview) ctx.onImageExpand(preview); + }} + className="block aspect-[4/3] w-full" + videoClassName="aspect-auto size-full rounded-lg border border-border/80" + stateClassName="aspect-auto min-h-full rounded-lg border border-border/80 bg-black text-white" + onRetry={asset ? refreshAssetUrl : undefined} + actionsSource={asset ? { kind: "video", name: file.name, src, asset } : undefined} + /> + ); +} + +export function UserMessageRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + return ctx.workflowOpening?.messageId === row.message.id ? ( + ctx.onOpenThread(ThreadId.make(threadId))} + /> + ) : ( + + ); +} + +function UserTimelineRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + const { onImageExpand, onFileOpen } = ctx; + const senderThreadId = row.message.senderThreadId; + const resources = selectMessageImageResources(row.message.attachments); + const previewUrls = useAssetUrls(ctx.activeThreadEnvironmentId, resources); + const [projectPreviews] = useState(createMessageAttachmentPreviewProjector); + const previewUrlsById = new Map( + resources.flatMap((resource, index) => { + const url = previewUrls[index]; + return url ? [[resource.attachmentId, url] as const] : []; + }), + ); + const messageWithPreviews = projectPreviews(row.message, (attachment) => + previewUrlsById.get(attachment.id), + ); + const userImages = (messageWithPreviews.attachments ?? []).filter(isImageAttachment); + const userFiles = (row.message.attachments ?? []).filter(isFileAttachment); + const userVideos = userFiles.filter(isVideoAttachment); + const otherUserFiles = userFiles.filter((file) => !isVideoAttachment(file)); + const unknownAttachments = (row.message.attachments ?? []).filter( + (attachment) => !isImageAttachment(attachment) && !isFileAttachment(attachment), + ); + const userMessage = resolveUserMessagePresentation(row.message); + const resolvedContext = resolveUserMessageContext(row.message); + const previewImages = userImages.filter((image) => image.name.startsWith("preview-annotation-")); + const revertTurnCount = row.revertTurnCount; + const chippedAttachmentIds = new Set( + collectComposerContextReferences(resolvedContext.text).flatMap((occurrence) => { + const record = asKnownContextRecord(resolvedContext.recordsById.get(occurrence.contextId)); + return record?.kind === "file" || record?.kind === "image" ? [record.attachmentId] : []; + }), + ); + const regularImages = userImages.filter((image) => !image.name.startsWith("preview-annotation-")); + const unchippedFiles = otherUserFiles.filter((file) => !chippedAttachmentIds.has(file.id)); + const annotationRecordIds = resolvedContext.records + .filter((record) => record.kind === "preview-annotation") + .map((record) => record.contextId); + const plainText = replaceComposerContextReferences( + resolvedContext.text, + (reference) => reference.label, + ); + const promptLink = promptLinkFor(plainText); + const contextClipboardFragment = + resolvedContext.records.length === 0 + ? null + : encodeComposerContextFragment({ + version: 1, + source: { + environmentId: ctx.activeThreadEnvironmentId, + ...(ctx.threadRef ? { threadId: ctx.threadRef.threadId } : {}), + messageId: row.message.id, + }, + records: resolvedContext.records, + }); + const onBodyCopyCapture = (event: React.ClipboardEvent) => { + if (resolvedContext.records.length === 0 || !event.clipboardData) return; + const selection = window.getSelection(); + if (!selection || selection.isCollapsed) return; + const copiedMarkdown: string[] = []; + for (let index = 0; index < selection.rangeCount; index += 1) { + const container = document.createElement("div"); + container.appendChild(selection.getRangeAt(index).cloneContents()); + // Diagram source is copied text, not authority to export context records. + for (const element of container.querySelectorAll( + "[data-markdown-copy]:not([data-markdown-mermaid])", + )) { + copiedMarkdown.push(element.getAttribute("data-markdown-copy") ?? ""); + } + } + const fragment = selectedMessageContextFragment({ + markdown: copiedMarkdown.join("\n"), + records: resolvedContext.records, + environmentId: ctx.activeThreadEnvironmentId, + ...(ctx.threadRef ? { threadId: ctx.threadRef.threadId } : {}), + messageId: row.message.id, + }); + if (!fragment) return; + // Claim the copy: without preventDefault the browser default overwrites the + // custom MIME type. The default content must then be written back explicitly. + const payload = chatMarkdownClipboardPayload(selection); + event.preventDefault(); + event.clipboardData.setData("text/plain", payload?.text ?? selection.toString()); + if (payload) { + event.clipboardData.setData( + "text/html", + encodeComposerContextClipboardHtml(payload.text, fragment, payload.html), + ); + } + event.clipboardData.setData(COMPOSER_CONTEXT_CLIPBOARD_MIME, fragment); + }; + const renderContextReference = (reference: ChatMarkdownContextReference) => { + const record = asKnownContextRecord(resolvedContext.recordsById.get(reference.contextId)); + const annotationImage = + record?.kind === "preview-annotation" + ? resolvePreviewAnnotationImage({ + record, + recordsById: resolvedContext.recordsById, + userImages, + previewImages, + annotationRecordIds, + }) + : null; + const attachment = + record?.kind === "image" + ? (userImages.find((image) => image.id === record.attachmentId) ?? null) + : record?.kind === "file" + ? (userFiles.find((file) => file.id === record.attachmentId) ?? null) + : null; + return ( + + { + const preview = buildExpandedImagePreview(userImages, image.id); + if (preview) onImageExpand(preview); + }} + onOpenFile={onFileOpen} + onExpandVideo={(file) => { + const preview = buildAttachmentVideoPreview(ctx.activeThreadEnvironmentId, file); + if (preview) onImageExpand(preview); + }} + /> + + ); + }; + + return ( +
+ {userMessage.attribution === "automation" ? ( +

+ {userMessage.scheduledTaskId ? ( + + Sent by automation + + ) : ( + "Sent by automation" + )} +

+ ) : userMessage.attribution === "t3code" ? ( +

+ Sent by T3 Code +

+ ) : userMessage.attribution === "agent" ? ( +

+ {senderThreadId ? ( + ctx.onOpenThread(senderThreadId)} + tone="muted" + aria-label="Open sending thread" + > + Sent by another agent + + ) : ( + "Sent by another agent" + )} +

+ ) : null} + {row.message.inputIntent === "steer" || + row.message.inputIntent === "promoted_queued_to_steer" ? ( + + ) : null} +
+ You + {(regularImages.length > 0 || userVideos.length > 0) && ( +
+ {regularImages.map((image) => ( +
+ {image.previewUrl ? ( + + ) : ( +
+ {image.name} +
+ )} + {image.previewUrl && image.source?.kind === "snap-shot" ? ( + + ) : null} +
+ ))} + {userVideos.map((file) => ( + + ))} +
+ )} + {unchippedFiles.length > 0 || unknownAttachments.length > 0 ? ( +
+ {unchippedFiles.map((file) => { + const fileIdentity = ( + <> + + {file.name} + + ); + if (file.downloadable !== false) { + return ( +
+ + + ctx.onFileDownload(file)} + /> + } + > + + + Download {file.name} + +
+ ); + } + + return ( +
+ {fileIdentity} +
+ ); + })} + {unknownAttachments.map((attachment) => ( +
+ + {attachment.name} +
+ ))} +
+ ) : null} +
+ +
+
+ {row.projectedItem && + row.projectedItem.item.status !== "completed" && + row.projectedItem.item.status !== "pending" && + row.projectedItem.item.status !== "waiting" ? ( +
+ + {row.projectedItem.item.status} + +
+ ) : null} +
+
+ + }> + {formatDayAwareTimestamp(row.message.createdAt, ctx.timestampFormat)} + + + {formatChatTimestampTooltip(row.message.createdAt, ctx.timestampFormat)} + + +
+ {typeof revertTurnCount === "number" && ( + + )} + {resolvedContext.text && ( + + )} + {promptLink !== null && ( + + )} +
+
+
+
+ ); +} + +function UserMessageIntentMarker({ + intent, +}: { + readonly intent: "steer" | "promoted_queued_to_steer"; +}) { + return ( + + + } + > + + + {intent === "promoted_queued_to_steer" + ? "Originally queued, then promoted to steer the active turn" + : "Steered the active turn"} + + + ); +} + +function RevertUserMessageButton({ + turnCount, + messageId, +}: { + turnCount: number; + messageId: MessageId; +}) { + const ctx = use(TimelineRowCtx); + const activity = use(TimelineRowActivityCtx); + + return ( + + ctx.onRevertToTurnCount(turnCount, messageId)} + aria-label="Edit from here" + /> + } + > + + + Edit from here + + ); +} + +const COLLAPSED_USER_MESSAGE_FADE_HEIGHT_REM = 1.75; + +const COLLAPSED_USER_MESSAGE_FADE_MASK = `linear-gradient(to bottom, black calc(100% - ${COLLAPSED_USER_MESSAGE_FADE_HEIGHT_REM}rem), transparent)`; + +const CollapsibleUserMessageBody = memo(function CollapsibleUserMessageBody(props: { + text: string; + renderContextReference: (reference: ChatMarkdownContextReference) => ReactNode; + skills: ReadonlyArray>; + markdownCwd: string | undefined; + footer?: ReactNode; +}) { + const [expanded, setExpanded] = useState(false); + const revealForFind = () => setExpanded(true); + const revealRef = findRevealRef(revealForFind); + const hasVisibleBody = props.text.trim().length > 0; + const canCollapse = hasVisibleBody && shouldCollapseUserMessage(props.text); + const isCollapsed = canCollapse && !expanded; + const showCollapseControl = canCollapse; + + return ( +
+ {hasVisibleBody ? ( +
+ +
+ ) : null} + {showCollapseControl || props.footer ? ( +
+ {showCollapseControl ? ( + + ) : null} + {props.footer ? ( +
{props.footer}
+ ) : null} +
+ ) : null} +
+ ); +}); + +const UserMessageBody = memo(function UserMessageBody(props: { + text: string; + renderContextReference?: (reference: ChatMarkdownContextReference) => ReactNode; + skills: ReadonlyArray>; + markdownCwd: string | undefined; +}) { + const ctx = use(TimelineRowCtx); + if (props.text.length === 0) { + return null; + } + return ( + + ); +}); diff --git a/apps/web/src/components/chat/UserMessageContextChips.tsx b/apps/web/src/components/chat/UserMessageContextChips.tsx new file mode 100644 index 0000000000000..cc5474c37901b --- /dev/null +++ b/apps/web/src/components/chat/UserMessageContextChips.tsx @@ -0,0 +1,592 @@ +import { useRenderedAppearance } from "../../hooks/useTheme"; +import { PullRequestGlyph } from "../pullRequest/pullRequestIcons"; +import { ReadOnlySourcePreview } from "../files/AttachmentFilePreview"; +import { useRightPanelStore } from "~/rightPanelStore"; +import { COMPOSER_CONTEXT_KINDS } from "@t3tools/contracts"; +import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments"; +import { use, type ReactNode } from "react"; +import { FileDiff } from "@pierre/diffs/react"; +import { DiffWorkerPoolProvider } from "../DiffWorkerPoolProvider"; +import { + type ChatFileAttachment, + type ChatImageAttachment, + isImageAttachment, + isVideoAttachment, +} from "../../types"; +import { + getRenderablePatch, + resolveDiffThemeName, + resolveFileDiffPath, +} from "../../lib/diffRendering"; +import { PREFERRED_HIGHLIGHTER } from "../../lib/syntaxHighlighting"; +import ChatMarkdown from "../ChatMarkdown"; +import { ThreadContextChip } from "../ThreadContextChip"; +import { MessageCircleIcon, MousePointerClickIcon, PaintbrushIcon } from "lucide-react"; +import type { ComposerContextId, KnownComposerContextRecord } from "@t3tools/contracts"; +import { buildExpandedImagePreview } from "./ExpandedImagePreview"; +import { WorkspaceEntryIcon, WorkspaceEntryTooltip } from "./WorkspaceEntryIcon"; +import { TerminalContextInlineChip } from "./TerminalContextInlineChip"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { + ContextChipPopover as UserMessageContextPopover, + ContextChipShell, + FileChip, + ImageChipButton, + PULL_REQUEST_CHIP_KINDS, + PullRequestChip, + UnresolvedChip, +} from "../contextChipParts"; +import { + isPullRequestSummaryContext, + pullRequestContextDisplayState, + pullRequestContextKindLabel, + reviewCommentContextLabel, +} from "~/lib/composerContextRecords"; +import { formatComposerContextReference } from "@t3tools/shared/composerContextReferences"; +import { ContextChip, ContextChipLabel, type ContextChipKind } from "../ContextChip"; +import { createContextPresentationRegistry } from "../contextPresentationRegistry"; +import type { ChatMarkdownContextReference } from "../ChatMarkdown"; +import { SkillChipIcon } from "./SkillInlineText"; +import { formatWorkspaceRelativePath } from "../../filePathDisplay"; +import { + buildReviewCommentRenderablePatch, + formatReviewCommentFence, + type ReviewCommentContext, +} from "../../reviewCommentContext"; +import { TimelineRowCtx } from "./timelineRowContext"; + +function UserMessageMentionChip(props: { + record: Extract; + copyMarkdown: string; +}) { + const ctx = use(TimelineRowCtx); + return ( + + } + aria-label={`Preview ${props.record.path}`} + data-markdown-copy={props.copyMarkdown} + onClick={() => { + if (ctx.threadRef) + useRightPanelStore.getState().openFile(ctx.threadRef, props.record.path); + }} + > + + {props.record.label} + + } + /> + + + + + ); +} + +function UserMessageContextChip(props: { + icon: ReactNode; + label: string; + kindLabel?: string; + copyMarkdown: string; + tooltip?: string; + kind: ContextChipKind; +}) { + return ( + + ); +} + +function UserMessagePullRequestContextChip(props: { + record: Extract; + copyMarkdown: string; + kind: ContextChipKind; +}) { + const { activeThreadEnvironmentId, openPullRequest } = use(TimelineRowCtx); + const metadata = props.record.pullRequest; + if (metadata === undefined) return null; + return ( + + ); +} + +function UserMessagePreviewAnnotationDetails(props: { + record: Extract; + image: ChatImageAttachment | null; +}) { + const ctx = use(TimelineRowCtx); + const visibleElements = props.record.elements ?? []; + return ( +
+ {props.image?.previewUrl ? ( + + ) : ( +
+ Screenshot unavailable +
+ )} +
+
+ {props.record.pageTitle?.trim() || props.record.pageUrl || "Preview annotation"} +
+ {props.record.comment ? ( +
+ {props.record.comment} +
+ ) : null} +
+ {props.record.targetSummary ? ( + {props.record.targetSummary} + ) : null} + {(props.record.styleChanges?.length ?? 0) > 0 ? ( + + + {props.record.styleChanges?.length ?? 0} + + ) : null} +
+ {visibleElements.length > 0 ? ( +
+ {visibleElements.map((element) => { + const source = element.source; + const sourceLabel = source?.fileName + ? `${source.fileName}${source.lineNumber === null ? "" : `:${source.lineNumber}`}` + : null; + return ( +
+
+ + {element.selector || `<${element.tagName}>`} + + {sourceLabel ? ( + {sourceLabel} + ) : null} +
+ {element.htmlPreview?.trim() ? ( +
+                      {element.htmlPreview.trim()}
+                    
+ ) : null} +
+ ); + })} + {(props.record.elements?.length ?? 0) > visibleElements.length ? ( +
+ {(props.record.elements?.length ?? 0) - visibleElements.length} more selected + elements +
+ ) : null} +
+ ) : null} +
+
+ ); +} + +function UserMessageElementDetails({ + record, +}: { + record: Extract; +}) { + const sourceLabel = record.source?.fileName + ? `${record.source.fileName}${record.source.lineNumber === null ? "" : `:${record.source.lineNumber}`}` + : null; + return ( +
+
+
+ {record.pageTitle?.trim() || record.pageUrl} +
+
{record.pageUrl}
+
+
+
+ + {record.selector || `<${record.tagName}>`} + + {sourceLabel ? ( + {sourceLabel} + ) : null} +
+ {record.htmlPreview?.trim() ? ( +
+ +
+ ) : null} + {record.styles?.trim() ? ( +
+ +
+ ) : null} +
+
+ ); +} + +interface UserMessageContextRenderContext { + reference: ChatMarkdownContextReference; + annotationImage: ChatImageAttachment | null; + attachment: ChatImageAttachment | ChatFileAttachment | null; + resolvedTheme: "light" | "dark"; + copyMarkdown: string; + onExpandImage: (image: ChatImageAttachment) => void; + onExpandVideo: (file: ChatFileAttachment) => void; + onOpenFile: (file: ChatFileAttachment) => void; +} + +function UnavailableUserMessageContextChip(props: UserMessageContextRenderContext) { + return ( + + ); +} + +const userMessageContextPresentationRegistry = createContextPresentationRegistry< + KnownComposerContextRecord, + UserMessageContextRenderContext, + ReactNode +>({ + requiredKinds: COMPOSER_CONTEXT_KINDS, + handlers: [ + { + kind: "mention", + canRender: (record) => record.kind === "mention", + render: (record, context) => + record.kind === "mention" ? ( + + ) : ( + + ), + }, + { + kind: "skill", + canRender: (record) => record.kind === "skill", + render: (record, context) => + record.kind === "skill" ? ( + } + label={record.label || record.name} + kindLabel="Skill" + tooltip={`$${record.name}`} + copyMarkdown={context.copyMarkdown} + kind="skill" + /> + ) : ( + + ), + }, + { + kind: "thread", + canRender: (record) => record.kind === "thread", + render: (record, context) => + record.kind === "thread" ? ( + + ) : ( + + ), + }, + { + kind: "image", + canRender: (record, context) => + record.kind === "image" && + context.attachment !== null && + isImageAttachment(context.attachment), + render: (record, context) => { + if ( + record.kind !== "image" || + context.attachment === null || + !isImageAttachment(context.attachment) + ) { + return ; + } + const attachment = context.attachment; + return ( + context.onExpandImage(attachment)} + /> + ); + }, + }, + { + kind: "file", + canRender: (record, context) => + record.kind === "file" && context.attachment !== null && context.attachment.type === "file", + render: (record, context) => { + if ( + record.kind !== "file" || + context.attachment === null || + context.attachment.type !== "file" + ) { + return ; + } + const attachment = context.attachment; + const isVideo = isVideoAttachment(attachment); + const disabled = + attachment.downloadable === false && (!isVideo || attachment.previewUrl === undefined); + const size = formatAttachmentSize(record.sizeBytes); + return ( + + isVideo ? context.onExpandVideo(attachment) : context.onOpenFile(attachment) + } + tooltip={`${record.name}\n${size}`} + /> + ); + }, + }, + { + kind: "terminal", + canRender: (record) => record.kind === "terminal", + render: (record, context, definition) => + record.kind === "terminal" ? ( + + + + ) : ( + + ), + }, + { + kind: "element", + canRender: (record) => record.kind === "element", + render: (record, context) => + record.kind === "element" ? ( + } + label={record.label} + > + + + ) : ( + + ), + }, + { + kind: "review-comment", + canRender: (record) => record.kind === "review-comment", + render: (record, context) => { + if (record.kind !== "review-comment") { + return ; + } + const isPullRequest = isPullRequestSummaryContext(record); + const label = reviewCommentContextLabel(record); + const kindLabel = isPullRequest ? pullRequestContextKindLabel(record) : "Review comment"; + const pullRequestState = pullRequestContextDisplayState(record) ?? "unknown"; + if (isPullRequest && record.pullRequest !== undefined) { + return ( + + ); + } + return ( + : } + label={label} + > + + + ); + }, + }, + { + kind: "preview-annotation", + canRender: (record) => record.kind === "preview-annotation", + render: (record, context) => + record.kind === "preview-annotation" ? ( + } + label={record.label} + > + + + ) : ( + + ), + }, + ], + fallback: (_kind, _record, context) => , +}); + +export function UserMessageContextReferenceChip(props: { + reference: ChatMarkdownContextReference; + record: KnownComposerContextRecord | undefined; + annotationImage: ChatImageAttachment | null; + attachment: ChatImageAttachment | ChatFileAttachment | null; + onExpandImage: (image: ChatImageAttachment) => void; + onExpandVideo: (file: ChatFileAttachment) => void; + onOpenFile: (file: ChatFileAttachment) => void; +}) { + const { resolvedTheme } = use(TimelineRowCtx); + const copyMarkdown = formatComposerContextReference({ + kind: props.reference.kind, + contextId: props.reference.contextId as ComposerContextId, + label: props.reference.label, + }); + return userMessageContextPresentationRegistry.render(props.reference.kind, props.record, { + reference: props.reference, + annotationImage: props.annotationImage, + attachment: props.attachment, + resolvedTheme, + copyMarkdown, + onExpandImage: props.onExpandImage, + onExpandVideo: props.onExpandVideo, + onOpenFile: props.onOpenFile, + }); +} + +function UserMessageReviewCommentCard({ comment }: { comment: ReviewCommentContext }) { + const renderedAppearance = useRenderedAppearance(); + const ctx = use(TimelineRowCtx); + const fenceLanguage = comment.fenceLanguage ?? "diff"; + const renderablePatch = getRenderablePatch( + buildReviewCommentRenderablePatch(comment), + `review-comment:${comment.id}`, + ); + + return ( +
+
+
+ {formatWorkspaceRelativePath(comment.filePath, ctx.workspaceRoot)} +
+
+ {comment.sectionTitle} · {comment.rangeLabel} +
+
+ {comment.text.length > 0 && ( +
+ +
+ )} + {fenceLanguage !== "diff" && comment.diff.trim().length > 0 && ( + + )} + {renderablePatch?.kind === "files" && ( + + {renderablePatch.files.map((fileDiff) => ( + + ))} + + )} + {renderablePatch?.kind === "raw" && ( +
+          {renderablePatch.text}
+        
+ )} +
+ ); +} From 476e2c3e3044ec431bf2d65337e0713b40fa8142 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:50 -0700 Subject: [PATCH 012/179] refactor(web): timeline assistant and orchestration event rows live in their own files (#22677) Co-authored-by: Claude Opus 5.5 --- .../src/components/chat/MessagesTimeline.tsx | 824 +----------------- .../components/chat/TimelineAssistantRows.tsx | 366 ++++++++ .../chat/TimelineV2EventRow.logic.ts | 128 +++ .../components/chat/TimelineV2EventRow.tsx | 321 +++++++ 4 files changed, 831 insertions(+), 808 deletions(-) create mode 100644 apps/web/src/components/chat/TimelineAssistantRows.tsx create mode 100644 apps/web/src/components/chat/TimelineV2EventRow.logic.ts create mode 100644 apps/web/src/components/chat/TimelineV2EventRow.tsx diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index c707e369eb442..ae09911bd9ae7 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -1,6 +1,5 @@ import { ThreadFindTimelineContext } from "./ThreadFindProvider"; import { shallow } from "zustand/vanilla/shallow"; -import { shouldPreserveAssistantLineBreaks } from "@t3tools/shared/markdownPipeline"; import { MarkdownFindContext } from "./markdownFindContext"; import { useChatCanvas } from "./ChatCanvasContext"; import { WorkLogBlock } from "./WorkLog"; @@ -16,15 +15,8 @@ import { type RunId, ThreadId, } from "@t3tools/contracts"; -import { parseScopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime/environment"; -import { useAtomValue } from "@effect/atom-react"; -import { environmentThreadDetails } from "../../state/threads"; -import { canForkProjectedAssistantItem } from "@t3tools/client-runtime/state/thread-workflows"; +import { parseScopedThreadKey } from "@t3tools/client-runtime/environment"; import { resolveWorkGroupScrollAnchor } from "@t3tools/client-runtime/work-log/scroll-anchor"; -import { - subagentGroupSummary, - summarizeSubagentStatuses, -} from "@t3tools/client-runtime/state/subagent-display"; import type { WorkflowOpening } from "@t3tools/client-runtime/state/subagentRuntime"; import { observeResize } from "~/lib/observeResize"; import { useStableCallback } from "~/hooks/useStableCallback"; @@ -49,34 +41,13 @@ import { type LegendListRef, type MaintainScrollAtEndOptions, } from "@legendapp/list/react"; -import { type TimelineEntry, providerErrorPresentation } from "../../session-logic"; +import { type TimelineEntry } from "../../session-logic"; import type { CodexArtifactTemplate } from "@t3tools/shared/codexArtifactTemplates"; -import { type ChatMessage, type ChatFileAttachment, type TurnDiffSummary } from "../../types"; -import ChatMarkdown from "../ChatMarkdown"; -import { - ArrowRightIcon, - BotIcon, - CheckIcon, - ChevronDownIcon, - CircleAlertIcon, - GitForkIcon, - type LucideIcon, - MinusIcon, - Minimize2Icon, - WrenchIcon, - XIcon, - ZapIcon, -} from "lucide-react"; +import { type ChatFileAttachment, type TurnDiffSummary } from "../../types"; +import { ArrowRightIcon, BotIcon, Minimize2Icon } from "lucide-react"; import { ChevronDown, ChevronRight } from "lucide"; -import { Button } from "../ui/button"; import { MorphIcon } from "~/components/MorphIcon"; import { ExpandedImagePreview } from "./ExpandedImagePreview"; -import { ProposedPlanCard } from "./ProposedPlanCard"; -import { HtmlRenderFrame } from "./HtmlRenderFrame"; -import { McpAppFrame } from "./McpAppFrame"; -import { ChangedFilesCard } from "./ChangedFilesTree"; -import { useFileContextMenuHandler } from "../../fileContextMenu"; -import { useProject, useThreadShell } from "../../state/entities"; import { CHAT_TIMELINE_ANCHOR_OFFSET, readTimelinePosition, @@ -84,16 +55,14 @@ import { rememberTimelinePosition, timelineContentOverflowsViewport, } from "./timelineScrollAnchoring"; -import { MessageCopyButton } from "./MessageCopyButton"; import { AssistantSelectionToolbar } from "./AssistantSelectionToolbar"; import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection"; -import { AssistantCitationSource, type AssistantCitationRequest } from "./AssistantCitationSource"; +import { type AssistantCitationRequest } from "./AssistantCitationSource"; import { useAssistantCitationTarget, type CitationHistoryPage } from "./useAssistantCitationTarget"; import { computeStableMessagesTimelineRows, deriveMessagesTimelineRowsWithState, type MessagesTimelineRowsProjection, - resolveAssistantMessageCopyState, resolveTimelineIsAtEnd, resolveTimelineMinimapHasPersistentGutter, resolveTimelineMinimapCurrentIndex, @@ -110,29 +79,16 @@ import { type ThreadFindMatch } from "./threadFind"; import { useThreadFindNavigation } from "./useThreadFindNavigation"; import { readThreadFindPosition } from "./threadFindPosition"; import type { ThreadFindPositionReader } from "./threadFind"; -import { Tooltip, TooltipPopup, TooltipTrigger, TooltipScrollDismissArea } from "../ui/tooltip"; +import { TooltipScrollDismissArea } from "../ui/tooltip"; import { WorktreeSetupCard } from "./WorktreeSetupCard"; import { useOpenPrLink } from "~/lib/openPullRequestLink"; import { useClientSettings } from "~/hooks/useSettings"; import { useMediaQuery } from "~/hooks/useMediaQuery"; import { cn } from "~/lib/utils"; -import { useUiStateStore } from "~/uiStateStore"; import { type TimestampFormat } from "@t3tools/contracts/settings"; -import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; -import { V2ItemInspector } from "./V2ItemInspector"; -import { useV2ItemSupport } from "../../state/v2ItemSupport"; -import { Collapsible, CollapsibleTrigger, CollapsiblePanel } from "../ui/collapsible"; -import { - isV2LifecycleItem, - EnvironmentSubagentAvatar, - SubagentElapsed, - V2LifecycleRow, - type HandoffTimelineRun, -} from "./V2LifecycleRow"; -import { SecretRequestCard } from "./SecretRequestCard"; +import { type HandoffTimelineRun } from "./V2LifecycleRow"; import { TimelineSystemDivider } from "./TimelineSystemDivider"; -import * as DateTime from "effect/DateTime"; import { forwardWheelToTimeline } from "./timelineMinimapWheel"; import { TimelineMinimap } from "./TimelineMinimap"; import { @@ -143,12 +99,7 @@ import { type WorkGroupViewState, type TimelineRow, } from "./timelineRowContext"; -import { - MESSAGE_HEADING_LEVEL, - MessageAuthorHeading, - TimelineRowTimestamp, - ActivityShimmerOverlay, -} from "./TimelineRowParts"; +import { TimelineRowTimestamp, ActivityShimmerOverlay } from "./TimelineRowParts"; import { WorkGroupSection, WorkingTimelineRow, @@ -157,6 +108,14 @@ import { WorkGroupToggleTimelineRow, } from "./TimelineWorkGroupRows"; import { UserMessageRow } from "./TimelineUserMessageRow"; +import { + AssistantTimelineRow, + AssistantMetaTimelineRow, + ProposedPlanTimelineRow, + HtmlRenderTimelineRow, + McpAppTimelineRow, +} from "./TimelineAssistantRows"; +import { V2EventTimelineRow } from "./TimelineV2EventRow"; function timelineListStateFor(identity: string): { readonly identity: string; @@ -1598,757 +1557,6 @@ function AttemptFoldTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const messageText = row.message.text || (row.message.streaming ? "" : "(empty response)"); - - return ( - <> -
- T3 Code -
- - - -
- - {row.showAssistantMeta ? ( - - ) : null} -
- - ); -} - -function AssistantForkButton({ - projectedItem, -}: { - readonly projectedItem: NonNullable["projectedItem"]>; -}) { - const ctx = use(TimelineRowCtx); - const [busy, setBusy] = useState(false); - const support = useV2ItemSupport({ - environmentId: ctx.activeThreadEnvironmentId, - sourceThreadId: projectedItem.sourceThreadId, - sourceItemId: projectedItem.sourceItemId, - }); - const canFork = canForkProjectedAssistantItem({ - projectedItem, - capabilities: support.providerSession?.capabilities, - }); - - if (!canFork || projectedItem.item.runId === null) return null; - const runId = projectedItem.item.runId; - - return ( - - { - setBusy(true); - void ctx - .onForkFromRun({ sourceThreadId: projectedItem.sourceThreadId, runId }) - .finally(() => setBusy(false)); - }} - aria-label="Fork from this response" - /> - } - > - - - Fork from this response - - ); -} - -function AssistantMetaTimelineRow({ - row, -}: { - row: Extract; -}) { - return ( -
- -
- ); -} - -function AssistantMessageMeta({ - className, - projectedItem, - message, - showCopyButton, - copyStreaming, - alwaysVisible = false, -}: { - className?: string; - projectedItem?: Extract["projectedItem"]; - message: ChatMessage; - showCopyButton: boolean; - copyStreaming: boolean; - alwaysVisible?: boolean; -}) { - const ctx = use(TimelineRowCtx); - - return ( -
- {projectedItem && projectedItem.item.status !== "completed" ? ( - - {projectedItem.item.status} - - ) : null} - - {projectedItem?.item.type === "assistant_message" ? ( - - ) : null} - {!message.streaming && ( - - }> - {formatDayAwareTimestamp(message.updatedAt, ctx.timestampFormat)} - - - {formatChatTimestampTooltip(message.updatedAt, ctx.timestampFormat)} - - - )} -
- ); -} - -function AssistantCopyButton({ - message, - showCopyButton, - streaming, -}: { - message: ChatMessage; - showCopyButton: boolean; - streaming: boolean; -}) { - const assistantCopyState = resolveAssistantMessageCopyState({ - text: message.text ?? null, - showCopyButton, - streaming, - }); - - if (!assistantCopyState.visible) { - return null; - } - - return ; -} - -function ProposedPlanTimelineRow({ - row, -}: { - row: Extract; -}) { - const ctx = use(TimelineRowCtx); - - return ( -
- -
- ); -} - -function HtmlRenderTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const { onAppFullscreenChange } = ctx; - const onFullscreenChange = (fullscreen: boolean) => onAppFullscreenChange(row.id, fullscreen); - - return ( -
- -
- ); -} - -function McpAppTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const { awaitingUser } = use(TimelineRowActivityCtx); - - return ( -
- ctx.onAppFullscreenChange(row.id, fullscreen)} - /> -
- ); -} - -type V2EventTone = "muted" | "warning" | "danger" | "success"; - -function v2EventPresentation(item: OrchestrationV2TurnItem): { - readonly label: string; - readonly detail: string | null; - readonly tone: V2EventTone; - readonly icon: LucideIcon; -} { - switch (item.type) { - case "error": { - const presentation = providerErrorPresentation(item); - return { - ...presentation, - tone: - item.status === "completed" - ? "success" - : item.status === "running" || item.failure.class === "usage_limit" - ? "warning" - : "danger", - icon: CircleAlertIcon, - }; - } - case "run_interrupt_request": - return { - label: "Interrupt requested", - detail: item.message, - tone: "warning", - icon: CircleAlertIcon, - }; - case "run_interrupt_result": - return { - label: "Run interrupted", - detail: item.message, - tone: "danger", - icon: XIcon, - }; - case "handoff": - return { - label: "Context handoff", - detail: - item.summary ?? - `${item.fromProviderInstanceIds.join(", ")} → ${item.toProviderInstanceId}`, - tone: item.status === "failed" ? "danger" : "muted", - icon: ZapIcon, - }; - case "fork": - return { - label: "Conversation fork", - detail: `Continues in ${item.targetThreadId}`, - tone: "muted", - icon: GitForkIcon, - }; - case "compaction": { - const tokenSummary = - item.beforeTokenCount === undefined && item.afterTokenCount === undefined - ? null - : `${item.beforeTokenCount ?? "?"} → ${item.afterTokenCount ?? "?"} tokens`; - return { - label: "Context compacted", - detail: item.summary ?? tokenSummary, - tone: item.status === "failed" ? "danger" : "muted", - icon: MinusIcon, - }; - } - case "todo_list": { - const steps = item.steps.map((step) => `${step.status}: ${step.text}`).join("\n"); - return { - label: "Plan updated", - detail: [item.explanation, steps].filter(Boolean).join("\n\n") || null, - tone: item.status === "failed" ? "danger" : "success", - icon: CheckIcon, - }; - } - default: - return { - label: item.title?.trim() || item.type.replaceAll("_", " "), - detail: null, - tone: item.status === "failed" ? "danger" : "muted", - icon: WrenchIcon, - }; - } -} - -function V2EventTimelineRow({ row }: { row: Extract }) { - const ctx = use(TimelineRowCtx); - const { item, visibility, sourceThreadId } = row.projectedItem; - if (item.type === "subagent" && (row.subagents?.length ?? 1) > 1) { - return ; - } - if (item.type === "secret_request") { - return ( - - ); - } - if (isV2LifecycleItem(item)) { - return ( - - ); - } - const presentation = v2EventPresentation(item); - const Icon = presentation.icon; - if (item.type === "error") { - return ( -
- - - {presentation.label} - {item.status !== "completed" ? ( - - {item.status} - - ) : null} - {presentation.detail ? ( - - {presentation.detail} - - ) : null} - {visibility !== "local" ? ( - - {visibility === "inherited" ? "Inherited" : "Synthetic"} - - ) : null} - - -
- {presentation.detail ? ( -
- -
- ) : null} - {visibility === "inherited" ? ( -

- From {sourceThreadId} -

- ) : null} -
- -
-
-
- ); - } - return ( -
-
- -
-
- {presentation.label} - {item.status !== "completed" ? ( - - {item.status} - - ) : null} - {visibility !== "local" ? ( - - {visibility === "inherited" ? "Inherited" : "Synthetic"} - - ) : null} -
- {presentation.detail ? ( -
- -
- ) : null} - {visibility === "inherited" ? ( -

- From {sourceThreadId} -

- ) : null} -
- -
-
-
-
- ); -} - -function subagentGroupTiming( - agents: ReadonlyArray<{ - status: OrchestrationV2TurnItem["status"]; - startedAt: DateTime.Utc | null; - completedAt: DateTime.Utc | null; - }>, -) { - let startMs: number | null = null; - let endMs: number | null = null; - let endUnknown = false; - for (const agent of agents) { - if (agent.startedAt) { - const ms = DateTime.toEpochMillis(agent.startedAt); - startMs = startMs === null ? ms : Math.min(startMs, ms); - } - if (agent.completedAt) { - const ms = DateTime.toEpochMillis(agent.completedAt); - endMs = endMs === null ? ms : Math.max(endMs, ms); - } else { - endUnknown = true; - } - } - const live = agents.some( - ({ status }) => status === "pending" || status === "running" || status === "waiting", - ); - return { - status: live ? ("running" as const) : ("completed" as const), - startedAt: startMs === null ? null : new Date(startMs).toISOString(), - completedAt: live || endUnknown || endMs === null ? null : new Date(endMs).toISOString(), - }; -} - -const V2SubagentGroup = memo(function V2SubagentGroup({ - row, -}: { - row: Extract; -}) { - const ctx = use(TimelineRowCtx); - const groupId = `subagent-group:${row.id}`; - const [expanded, setExpanded] = useState(() => - ctx.workGroupViewState.expandedEntries.has(groupId), - ); - const members = (row.subagents ?? [row.projectedItem]).flatMap(({ item }) => - item.type === "subagent" ? [item] : [], - ); - const liveAgents = useAtomValue( - environmentThreadDetails.threadAtom( - scopeThreadRef(ctx.activeThreadEnvironmentId, row.projectedItem.item.threadId), - ), - (thread) => thread?.projection.subagents, - ); - const agents = members.map((item) => { - const live = liveAgents?.find((agent) => agent.id === item.subagentId); - return { - item, - status: live?.status ?? item.status, - startedAt: live?.startedAt ?? item.startedAt, - completedAt: live?.completedAt ?? item.completedAt, - }; - }); - const summary = subagentGroupSummary(agents); - const label = `${members.length} ${members.length === 1 ? "subagent" : "subagents"}`; - const statusSummary = summarizeSubagentStatuses(agents.map(({ status }) => status)); - const toggleExpanded = (open: boolean) => { - ctx.onToggleWorkEntry(row.id, expanded); - if (open) ctx.workGroupViewState.expandedEntries.add(groupId); - else ctx.workGroupViewState.expandedEntries.delete(groupId); - setExpanded(open); - }; - return ( - - - - - {agents.slice(0, 3).map(({ item, status }) => ( - - ))} - {agents.length > 3 ? ( - - +{agents.length - 3} - - ) : null} - - - {label} - - {statusSummary} - - - - - - - - {/* Virtualized rows must settle before disclosure scroll anchoring resumes. */} - - {expanded ? ( -
- {members.map((item) => ( - - ))} -
- ) : null} -
-
-
- ); -}); - -/** Subscribes directly to the UI state store for expand/collapse state, - * so toggling re-renders only this component — not the entire list. */ -const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({ - turnSummary, - routeThreadKey, - resolvedTheme, - onOpenTurnDiff, -}: { - turnSummary: TurnDiffSummary | undefined; - routeThreadKey: string; - resolvedTheme: "light" | "dark"; - displayThreadKey?: string; - onOpenTurnDiff: (runId: RunId, filePath?: string) => void; -}) { - if (!turnSummary) return null; - const checkpointFiles = turnSummary.files; - if (checkpointFiles.length === 0) return null; - - return ( - - ); -}); - -function AssistantChangedFilesSectionInner({ - turnSummary, - checkpointFiles, - routeThreadKey, - resolvedTheme, - onOpenTurnDiff, -}: { - turnSummary: TurnDiffSummary; - checkpointFiles: TurnDiffSummary["files"]; - routeThreadKey: string; - resolvedTheme: "light" | "dark"; - displayThreadKey?: string; - onOpenTurnDiff: (runId: RunId, filePath?: string) => void; -}) { - const ctx = use(TimelineRowCtx); - const persistedExpanded = useUiStateStore( - (store) => store.threadChangedFilesExpandedById[routeThreadKey]?.[turnSummary.runId], - ); - const setExpanded = useUiStateStore((store) => store.setThreadChangedFilesExpanded); - const allDirectoriesExpanded = persistedExpanded ?? false; - - const thread = useThreadShell(ctx.threadRef); - const activeProject = useProject( - thread && thread.projectId - ? { environmentId: thread.environmentId, projectId: thread.projectId } - : null, - ); - const onFileContextMenu = useFileContextMenuHandler(ctx.activeThreadEnvironmentId); - - return ( - - setExpanded(routeThreadKey, turnSummary.runId, !allDirectoriesExpanded) - } - onOpenTurnDiff={onOpenTurnDiff} - onFileContextMenu={(filePath, event) => - onFileContextMenu( - { - environmentId: ctx.activeThreadEnvironmentId, - filePath, - workspaceRoot: ctx.workspaceRoot, - repositoryRoot: - thread?.worktreePath == null - ? activeProject?.repositoryIdentity?.rootPath - : undefined, - }, - event, - ) - } - /> - ); -} - function handoffRunsSignature(runs: ReadonlyArray): string { return runs .map( diff --git a/apps/web/src/components/chat/TimelineAssistantRows.tsx b/apps/web/src/components/chat/TimelineAssistantRows.tsx new file mode 100644 index 0000000000000..6f99213057f96 --- /dev/null +++ b/apps/web/src/components/chat/TimelineAssistantRows.tsx @@ -0,0 +1,366 @@ +import { shouldPreserveAssistantLineBreaks } from "@t3tools/shared/markdownPipeline"; +import { type RunId } from "@t3tools/contracts"; +import { canForkProjectedAssistantItem } from "@t3tools/client-runtime/state/thread-workflows"; +import { memo, use, useState } from "react"; +import { type ChatMessage, type TurnDiffSummary } from "../../types"; +import ChatMarkdown from "../ChatMarkdown"; +import { GitForkIcon } from "lucide-react"; +import { Button } from "../ui/button"; +import { ProposedPlanCard } from "./ProposedPlanCard"; +import { HtmlRenderFrame } from "./HtmlRenderFrame"; +import { McpAppFrame } from "./McpAppFrame"; +import { ChangedFilesCard } from "./ChangedFilesTree"; +import { useFileContextMenuHandler } from "../../fileContextMenu"; +import { useProject, useThreadShell } from "../../state/entities"; +import { MessageCopyButton } from "./MessageCopyButton"; +import { AssistantCitationSource } from "./AssistantCitationSource"; +import { resolveAssistantMessageCopyState } from "./MessagesTimeline.logic"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; +import { cn } from "~/lib/utils"; +import { useUiStateStore } from "~/uiStateStore"; +import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; +import { useV2ItemSupport } from "../../state/v2ItemSupport"; +import { TimelineRowCtx, TimelineRowActivityCtx, type TimelineRow } from "./timelineRowContext"; +import { MESSAGE_HEADING_LEVEL, MessageAuthorHeading } from "./TimelineRowParts"; + +export function AssistantTimelineRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + const messageText = row.message.text || (row.message.streaming ? "" : "(empty response)"); + + return ( + <> +
+ T3 Code +
+ + + +
+ + {row.showAssistantMeta ? ( + + ) : null} +
+ + ); +} + +function AssistantForkButton({ + projectedItem, +}: { + readonly projectedItem: NonNullable["projectedItem"]>; +}) { + const ctx = use(TimelineRowCtx); + const [busy, setBusy] = useState(false); + const support = useV2ItemSupport({ + environmentId: ctx.activeThreadEnvironmentId, + sourceThreadId: projectedItem.sourceThreadId, + sourceItemId: projectedItem.sourceItemId, + }); + const canFork = canForkProjectedAssistantItem({ + projectedItem, + capabilities: support.providerSession?.capabilities, + }); + + if (!canFork || projectedItem.item.runId === null) return null; + const runId = projectedItem.item.runId; + + return ( + + { + setBusy(true); + void ctx + .onForkFromRun({ sourceThreadId: projectedItem.sourceThreadId, runId }) + .finally(() => setBusy(false)); + }} + aria-label="Fork from this response" + /> + } + > + + + Fork from this response + + ); +} + +export function AssistantMetaTimelineRow({ + row, +}: { + row: Extract; +}) { + return ( +
+ +
+ ); +} + +function AssistantMessageMeta({ + className, + projectedItem, + message, + showCopyButton, + copyStreaming, + alwaysVisible = false, +}: { + className?: string; + projectedItem?: Extract["projectedItem"]; + message: ChatMessage; + showCopyButton: boolean; + copyStreaming: boolean; + alwaysVisible?: boolean; +}) { + const ctx = use(TimelineRowCtx); + + return ( +
+ {projectedItem && projectedItem.item.status !== "completed" ? ( + + {projectedItem.item.status} + + ) : null} + + {projectedItem?.item.type === "assistant_message" ? ( + + ) : null} + {!message.streaming && ( + + }> + {formatDayAwareTimestamp(message.updatedAt, ctx.timestampFormat)} + + + {formatChatTimestampTooltip(message.updatedAt, ctx.timestampFormat)} + + + )} +
+ ); +} + +function AssistantCopyButton({ + message, + showCopyButton, + streaming, +}: { + message: ChatMessage; + showCopyButton: boolean; + streaming: boolean; +}) { + const assistantCopyState = resolveAssistantMessageCopyState({ + text: message.text ?? null, + showCopyButton, + streaming, + }); + + if (!assistantCopyState.visible) { + return null; + } + + return ; +} + +export function ProposedPlanTimelineRow({ + row, +}: { + row: Extract; +}) { + const ctx = use(TimelineRowCtx); + + return ( +
+ +
+ ); +} + +export function HtmlRenderTimelineRow({ + row, +}: { + row: Extract; +}) { + const ctx = use(TimelineRowCtx); + const { onAppFullscreenChange } = ctx; + const onFullscreenChange = (fullscreen: boolean) => onAppFullscreenChange(row.id, fullscreen); + + return ( +
+ +
+ ); +} + +export function McpAppTimelineRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + const { awaitingUser } = use(TimelineRowActivityCtx); + + return ( +
+ ctx.onAppFullscreenChange(row.id, fullscreen)} + /> +
+ ); +} + +/** Subscribes directly to the UI state store for expand/collapse state, + * so toggling re-renders only this component — not the entire list. */ +const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({ + turnSummary, + routeThreadKey, + resolvedTheme, + onOpenTurnDiff, +}: { + turnSummary: TurnDiffSummary | undefined; + routeThreadKey: string; + resolvedTheme: "light" | "dark"; + displayThreadKey?: string; + onOpenTurnDiff: (runId: RunId, filePath?: string) => void; +}) { + if (!turnSummary) return null; + const checkpointFiles = turnSummary.files; + if (checkpointFiles.length === 0) return null; + + return ( + + ); +}); + +function AssistantChangedFilesSectionInner({ + turnSummary, + checkpointFiles, + routeThreadKey, + resolvedTheme, + onOpenTurnDiff, +}: { + turnSummary: TurnDiffSummary; + checkpointFiles: TurnDiffSummary["files"]; + routeThreadKey: string; + resolvedTheme: "light" | "dark"; + displayThreadKey?: string; + onOpenTurnDiff: (runId: RunId, filePath?: string) => void; +}) { + const ctx = use(TimelineRowCtx); + const persistedExpanded = useUiStateStore( + (store) => store.threadChangedFilesExpandedById[routeThreadKey]?.[turnSummary.runId], + ); + const setExpanded = useUiStateStore((store) => store.setThreadChangedFilesExpanded); + const allDirectoriesExpanded = persistedExpanded ?? false; + + const thread = useThreadShell(ctx.threadRef); + const activeProject = useProject( + thread && thread.projectId + ? { environmentId: thread.environmentId, projectId: thread.projectId } + : null, + ); + const onFileContextMenu = useFileContextMenuHandler(ctx.activeThreadEnvironmentId); + + return ( + + setExpanded(routeThreadKey, turnSummary.runId, !allDirectoriesExpanded) + } + onOpenTurnDiff={onOpenTurnDiff} + onFileContextMenu={(filePath, event) => + onFileContextMenu( + { + environmentId: ctx.activeThreadEnvironmentId, + filePath, + workspaceRoot: ctx.workspaceRoot, + repositoryRoot: + thread?.worktreePath == null + ? activeProject?.repositoryIdentity?.rootPath + : undefined, + }, + event, + ) + } + /> + ); +} diff --git a/apps/web/src/components/chat/TimelineV2EventRow.logic.ts b/apps/web/src/components/chat/TimelineV2EventRow.logic.ts new file mode 100644 index 0000000000000..fd12143ef30d0 --- /dev/null +++ b/apps/web/src/components/chat/TimelineV2EventRow.logic.ts @@ -0,0 +1,128 @@ +import { type OrchestrationV2TurnItem } from "@t3tools/contracts"; +import { providerErrorPresentation } from "../../session-logic"; +import { + CheckIcon, + CircleAlertIcon, + GitForkIcon, + type LucideIcon, + MinusIcon, + WrenchIcon, + XIcon, + ZapIcon, +} from "lucide-react"; +import * as DateTime from "effect/DateTime"; + +type V2EventTone = "muted" | "warning" | "danger" | "success"; + +export function v2EventPresentation(item: OrchestrationV2TurnItem): { + readonly label: string; + readonly detail: string | null; + readonly tone: V2EventTone; + readonly icon: LucideIcon; +} { + switch (item.type) { + case "error": { + const presentation = providerErrorPresentation(item); + return { + ...presentation, + tone: + item.status === "completed" + ? "success" + : item.status === "running" || item.failure.class === "usage_limit" + ? "warning" + : "danger", + icon: CircleAlertIcon, + }; + } + case "run_interrupt_request": + return { + label: "Interrupt requested", + detail: item.message, + tone: "warning", + icon: CircleAlertIcon, + }; + case "run_interrupt_result": + return { + label: "Run interrupted", + detail: item.message, + tone: "danger", + icon: XIcon, + }; + case "handoff": + return { + label: "Context handoff", + detail: + item.summary ?? + `${item.fromProviderInstanceIds.join(", ")} → ${item.toProviderInstanceId}`, + tone: item.status === "failed" ? "danger" : "muted", + icon: ZapIcon, + }; + case "fork": + return { + label: "Conversation fork", + detail: `Continues in ${item.targetThreadId}`, + tone: "muted", + icon: GitForkIcon, + }; + case "compaction": { + const tokenSummary = + item.beforeTokenCount === undefined && item.afterTokenCount === undefined + ? null + : `${item.beforeTokenCount ?? "?"} → ${item.afterTokenCount ?? "?"} tokens`; + return { + label: "Context compacted", + detail: item.summary ?? tokenSummary, + tone: item.status === "failed" ? "danger" : "muted", + icon: MinusIcon, + }; + } + case "todo_list": { + const steps = item.steps.map((step) => `${step.status}: ${step.text}`).join("\n"); + return { + label: "Plan updated", + detail: [item.explanation, steps].filter(Boolean).join("\n\n") || null, + tone: item.status === "failed" ? "danger" : "success", + icon: CheckIcon, + }; + } + default: + return { + label: item.title?.trim() || item.type.replaceAll("_", " "), + detail: null, + tone: item.status === "failed" ? "danger" : "muted", + icon: WrenchIcon, + }; + } +} + +export function subagentGroupTiming( + agents: ReadonlyArray<{ + status: OrchestrationV2TurnItem["status"]; + startedAt: DateTime.Utc | null; + completedAt: DateTime.Utc | null; + }>, +) { + let startMs: number | null = null; + let endMs: number | null = null; + let endUnknown = false; + for (const agent of agents) { + if (agent.startedAt) { + const ms = DateTime.toEpochMillis(agent.startedAt); + startMs = startMs === null ? ms : Math.min(startMs, ms); + } + if (agent.completedAt) { + const ms = DateTime.toEpochMillis(agent.completedAt); + endMs = endMs === null ? ms : Math.max(endMs, ms); + } else { + endUnknown = true; + } + } + const live = agents.some( + ({ status }) => status === "pending" || status === "running" || status === "waiting", + ); + return { + status: live ? ("running" as const) : ("completed" as const), + startedAt: startMs === null ? null : new Date(startMs).toISOString(), + completedAt: live || endUnknown || endMs === null ? null : new Date(endMs).toISOString(), + }; +} diff --git a/apps/web/src/components/chat/TimelineV2EventRow.tsx b/apps/web/src/components/chat/TimelineV2EventRow.tsx new file mode 100644 index 0000000000000..a4d2adf902f3c --- /dev/null +++ b/apps/web/src/components/chat/TimelineV2EventRow.tsx @@ -0,0 +1,321 @@ +import { WorkLogBlock } from "./WorkLog"; +import { scopeThreadRef } from "@t3tools/client-runtime/environment"; +import { useAtomValue } from "@effect/atom-react"; +import { environmentThreadDetails } from "../../state/threads"; +import { + subagentGroupSummary, + summarizeSubagentStatuses, +} from "@t3tools/client-runtime/state/subagent-display"; +import { memo, use, useState } from "react"; +import ChatMarkdown from "../ChatMarkdown"; +import { ChevronDownIcon } from "lucide-react"; +import { cn } from "~/lib/utils"; +import { V2ItemInspector } from "./V2ItemInspector"; +import { Collapsible, CollapsibleTrigger, CollapsiblePanel } from "../ui/collapsible"; +import { + isV2LifecycleItem, + EnvironmentSubagentAvatar, + SubagentElapsed, + V2LifecycleRow, +} from "./V2LifecycleRow"; +import { SecretRequestCard } from "./SecretRequestCard"; +import { TimelineRowCtx, type TimelineRow } from "./timelineRowContext"; +import { v2EventPresentation, subagentGroupTiming } from "./TimelineV2EventRow.logic"; + +export function V2EventTimelineRow({ row }: { row: Extract }) { + const ctx = use(TimelineRowCtx); + const { item, visibility, sourceThreadId } = row.projectedItem; + if (item.type === "subagent" && (row.subagents?.length ?? 1) > 1) { + return ; + } + if (item.type === "secret_request") { + return ( + + ); + } + if (isV2LifecycleItem(item)) { + return ( + + ); + } + const presentation = v2EventPresentation(item); + const Icon = presentation.icon; + if (item.type === "error") { + return ( +
+ + + {presentation.label} + {item.status !== "completed" ? ( + + {item.status} + + ) : null} + {presentation.detail ? ( + + {presentation.detail} + + ) : null} + {visibility !== "local" ? ( + + {visibility === "inherited" ? "Inherited" : "Synthetic"} + + ) : null} + + +
+ {presentation.detail ? ( +
+ +
+ ) : null} + {visibility === "inherited" ? ( +

+ From {sourceThreadId} +

+ ) : null} +
+ +
+
+
+ ); + } + return ( +
+
+ +
+
+ {presentation.label} + {item.status !== "completed" ? ( + + {item.status} + + ) : null} + {visibility !== "local" ? ( + + {visibility === "inherited" ? "Inherited" : "Synthetic"} + + ) : null} +
+ {presentation.detail ? ( +
+ +
+ ) : null} + {visibility === "inherited" ? ( +

+ From {sourceThreadId} +

+ ) : null} +
+ +
+
+
+
+ ); +} + +const V2SubagentGroup = memo(function V2SubagentGroup({ + row, +}: { + row: Extract; +}) { + const ctx = use(TimelineRowCtx); + const groupId = `subagent-group:${row.id}`; + const [expanded, setExpanded] = useState(() => + ctx.workGroupViewState.expandedEntries.has(groupId), + ); + const members = (row.subagents ?? [row.projectedItem]).flatMap(({ item }) => + item.type === "subagent" ? [item] : [], + ); + const liveAgents = useAtomValue( + environmentThreadDetails.threadAtom( + scopeThreadRef(ctx.activeThreadEnvironmentId, row.projectedItem.item.threadId), + ), + (thread) => thread?.projection.subagents, + ); + const agents = members.map((item) => { + const live = liveAgents?.find((agent) => agent.id === item.subagentId); + return { + item, + status: live?.status ?? item.status, + startedAt: live?.startedAt ?? item.startedAt, + completedAt: live?.completedAt ?? item.completedAt, + }; + }); + const summary = subagentGroupSummary(agents); + const label = `${members.length} ${members.length === 1 ? "subagent" : "subagents"}`; + const statusSummary = summarizeSubagentStatuses(agents.map(({ status }) => status)); + const toggleExpanded = (open: boolean) => { + ctx.onToggleWorkEntry(row.id, expanded); + if (open) ctx.workGroupViewState.expandedEntries.add(groupId); + else ctx.workGroupViewState.expandedEntries.delete(groupId); + setExpanded(open); + }; + return ( + + + + + {agents.slice(0, 3).map(({ item, status }) => ( + + ))} + {agents.length > 3 ? ( + + +{agents.length - 3} + + ) : null} + + + {label} + + {statusSummary} + + + + + + + + {/* Virtualized rows must settle before disclosure scroll anchoring resumes. */} + + {expanded ? ( +
+ {members.map((item) => ( + + ))} +
+ ) : null} +
+
+
+ ); +}); From bef9142f8b9685dab9e1d13cc6c6750a7c78b2d3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:12:51 -0700 Subject: [PATCH 013/179] refactor(web): timeline row derivation, position restore and find reader live in hooks (#22682) Co-authored-by: Claude Opus 5.5 --- .../components/chat/MessagesTimeline.logic.ts | 28 +- .../src/components/chat/MessagesTimeline.tsx | 304 ++---------------- .../chat/useThreadFindPositionReader.ts | 100 ++++++ .../chat/useTimelinePositionRestore.ts | 166 ++++++++++ .../src/components/chat/useTimelineRows.ts | 78 +++++ 5 files changed, 391 insertions(+), 285 deletions(-) create mode 100644 apps/web/src/components/chat/useThreadFindPositionReader.ts create mode 100644 apps/web/src/components/chat/useTimelinePositionRestore.ts create mode 100644 apps/web/src/components/chat/useTimelineRows.ts diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index 96c3a36a6e403..361bcdb62dbfb 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -8,6 +8,7 @@ import { collectAssistantCitations, } from "@t3tools/shared/assistantCitations"; import { shallow } from "zustand/vanilla/shallow"; +import type { HandoffTimelineRun } from "@t3tools/client-runtime/handoff"; import { renderCodexDirectivesForCopy } from "@t3tools/shared/codexMarkdownDirectives"; import { commandDisplayText, @@ -2011,7 +2012,7 @@ function attachCreatedThreadSummaries( const WORKTREE_SETUP_ROW_ID = "worktree-setup-row"; -type MessagesTimelineRowsInput = Parameters[0]; +export type MessagesTimelineRowsInput = Parameters[0]; export interface MessagesTimelineRowsProjection { readonly input: MessagesTimelineRowsInput; @@ -2314,3 +2315,28 @@ export function shouldCollapseUserMessage(text: string): boolean { visible.split("\n").length > MAX_COLLAPSED_USER_MESSAGE_LINES ); } + +export function handoffRunsSignature(runs: ReadonlyArray): string { + return runs + .map( + (run) => + `${run.id}\0${run.providerInstanceId}\0${run.ordinal}\0${run.modelSelection.instanceId}\0${run.modelSelection.model}`, + ) + .join("\n"); +} + +// Expanded-id sets can be rebuilt during render; comparing them by content keeps +// the render-time state update in useIncrementalTimelineRows from repeating forever. +export function sameRowsInput( + left: MessagesTimelineRowsInput, + right: MessagesTimelineRowsInput, +): boolean { + for (const key of Object.keys(right) as Array) { + const previous = left[key]; + const next = right[key]; + if (Object.is(previous, next)) continue; + if (previous instanceof Set && next instanceof Set && shallow(previous, next)) continue; + return false; + } + return Object.keys(left).length === Object.keys(right).length; +} diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index ae09911bd9ae7..8382c32248445 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -1,5 +1,4 @@ import { ThreadFindTimelineContext } from "./ThreadFindProvider"; -import { shallow } from "zustand/vanilla/shallow"; import { MarkdownFindContext } from "./markdownFindContext"; import { useChatCanvas } from "./ChatCanvasContext"; import { WorkLogBlock } from "./WorkLog"; @@ -60,9 +59,6 @@ import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection import { type AssistantCitationRequest } from "./AssistantCitationSource"; import { useAssistantCitationTarget, type CitationHistoryPage } from "./useAssistantCitationTarget"; import { - computeStableMessagesTimelineRows, - deriveMessagesTimelineRowsWithState, - type MessagesTimelineRowsProjection, resolveTimelineIsAtEnd, resolveTimelineMinimapHasPersistentGutter, resolveTimelineMinimapCurrentIndex, @@ -71,13 +67,11 @@ import { resolveTimelineMinimapHitStripWidth, timelineTurnFoldRunIdsByEntryId, worktreeSetupAgentStarted, - type StableMessagesTimelineRowsState, type MessagesTimelineRow, type TimelineLatestRun, } from "./MessagesTimeline.logic"; import { type ThreadFindMatch } from "./threadFind"; import { useThreadFindNavigation } from "./useThreadFindNavigation"; -import { readThreadFindPosition } from "./threadFindPosition"; import type { ThreadFindPositionReader } from "./threadFind"; import { TooltipScrollDismissArea } from "../ui/tooltip"; import { WorktreeSetupCard } from "./WorktreeSetupCard"; @@ -116,6 +110,9 @@ import { McpAppTimelineRow, } from "./TimelineAssistantRows"; import { V2EventTimelineRow } from "./TimelineV2EventRow"; +import { useTimelinePositionRestore } from "./useTimelinePositionRestore"; +import { useThreadFindPositionReader } from "./useThreadFindPositionReader"; +import { useStableHandoffRuns, useIncrementalTimelineRows, useStableRows } from "./useTimelineRows"; function timelineListStateFor(identity: string): { readonly identity: string; @@ -163,7 +160,6 @@ const TIMELINE_MAINTAIN_SCROLL_AT_END = { layout: true, }, } as const satisfies MaintainScrollAtEndOptions; -const EMPTY_TIMELINE_RUNS: ReadonlyArray = []; const EMPTY_RUN_IDS: ReadonlySet = new Set(); const TIMELINE_MAINTAIN_SCROLL_AT_END_SMOOTH = { ...TIMELINE_MAINTAIN_SCROLL_AT_END, @@ -609,145 +605,23 @@ const ConversationTimeline = memo(function ConversationTimeline({ const runs = useStableHandoffRuns(runsProp); const minimapItems = deriveTimelineMinimapItems(rows); const minimapRowIndices = minimapItems.map((item) => item.rowIndex); - // Preserve initialScrollAtEnd's measured target until the list is reused - // for another thread; scrollToEnd would replace it with an estimated offset. - const listOpenedOnRef = useRef<{ - readonly list: LegendListRef; - identity: string | null; - } | null>(null); - useLayoutEffect(() => { - const list = listRef.current; - const opened = listOpenedOnRef.current; - if (!list) return; - if (opened?.list !== list) { - listOpenedOnRef.current = { list, identity: listIdentityKey }; - } else if (opened.identity !== listIdentityKey) { - opened.identity = null; - } - }); const restoreRowIndex = restoringThreadPosition && rememberedPosition?.atEnd === false ? rows.findIndex((row) => row.id === rememberedPosition.rowId) : -1; const restoringAlwaysRender = restoringThreadPosition && restoreRowIndex >= 0 ? { indices: [restoreRowIndex] } : undefined; - useLayoutEffect(() => { - if (!restoringThreadPosition || rows.length === 0) return; - const list = listRef.current; - if (!list) return; - if (citationRequest !== null) { - // oxlint-disable-next-line react/set-state-in-effect -- The citation scroll owns positioning once the list has mounted. - setPositionedThreadKey(listIdentityKey); - return; - } - let cancelled = false; - let settleFrame: number | null = null; - const viewport: HTMLElement | null = list.getScrollableNode(); - const cancelRestoration = () => { - if (cancelled) return; - cancelled = true; - if (settleFrame !== null) cancelAnimationFrame(settleFrame); - if (viewport) void list.scrollToOffset({ offset: viewport.scrollTop, animated: false }); - setPositionedThreadKey(listIdentityKey); - }; - const cancelForNavigation = () => { - cancelRestoration(); - onManualNavigation(); - }; - const onScrollKey = (event: globalThis.KeyboardEvent) => { - if ( - ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key) && - !( - event.target instanceof Element && - event.target.closest("input, textarea, [contenteditable=true]") - ) - ) - cancelForNavigation(); - }; - viewport?.addEventListener("wheel", cancelForNavigation, { passive: true }); - viewport?.addEventListener("touchmove", cancelForNavigation, { passive: true }); - viewport?.addEventListener("pointerdown", cancelForNavigation, { passive: true }); - viewport?.ownerDocument.addEventListener("keydown", onScrollKey); - const position = rememberedPosition; - const index = position ? rows.findIndex((row) => row.id === position.rowId) : -1; - if (position?.atEnd === false) onManualNavigation(); - if (cancelPositionRestoreRef) cancelPositionRestoreRef.current = cancelRestoration; - const scrolling = - position?.atEnd === false - ? index >= 0 - ? list.scrollToIndex({ - index, - animated: false, - viewPosition: 0, - viewOffset: -position.offsetWithinRow, - }) - : list.scrollToOffset({ offset: position.scrollOffset, animated: false }) - : listOpenedOnRef.current?.identity === listIdentityKey - ? undefined - : list.scrollToEnd({ animated: false }); - void Promise.resolve(scrolling).then(() => { - if (cancelled) return; - if (position?.atEnd !== false || index < 0) { - setPositionedThreadKey(listIdentityKey); - return; - } - // Index scrolling starts from estimates. Keep the saved row mounted - // until its measured position and the DOM agree for two layout frames. - let stableFrames = 0; - const reconcile = () => { - if (cancelled) return; - const state = list.getState(); - const rowIndex = state.indexByKey(position.rowId); - const row = rowIndex === undefined ? undefined : state.elementAtIndex(rowIndex); - const element = list.getScrollableNode(); - if (!row || !element) return; - const offset = Math.max( - 0, - Math.min( - element.scrollTop + - row.getBoundingClientRect().top - - element.getBoundingClientRect().top + - position.offsetWithinRow, - element.scrollHeight - element.clientHeight, - ), - ); - if (Math.abs(element.scrollTop - offset) > 1) { - stableFrames = 0; - void list.scrollToOffset({ offset, animated: false }).then(() => { - if (!cancelled) settleFrame = requestAnimationFrame(reconcile); - }); - return; - } - stableFrames += 1; - if (stableFrames >= 2) { - setPositionedThreadKey(listIdentityKey); - } else { - settleFrame = requestAnimationFrame(reconcile); - } - }; - settleFrame = requestAnimationFrame(reconcile); - }); - return () => { - cancelled = true; - if (cancelPositionRestoreRef?.current === cancelRestoration) { - cancelPositionRestoreRef.current = null; - } - if (settleFrame !== null) cancelAnimationFrame(settleFrame); - viewport?.removeEventListener("wheel", cancelForNavigation); - viewport?.removeEventListener("touchmove", cancelForNavigation); - viewport?.removeEventListener("pointerdown", cancelForNavigation); - viewport?.ownerDocument.removeEventListener("keydown", onScrollKey); - }; - }, [ - citationRequest, - cancelPositionRestoreRef, - listIdentityKey, + useTimelinePositionRestore({ listRef, - onManualNavigation, - rememberedPosition, + listIdentityKey, restoringThreadPosition, + rememberedPosition, rows, - ]); + citationRequest, + cancelPositionRestoreRef, + onManualNavigation, + onPositioned: setPositionedThreadKey, + }); const [timelineViewportElement, setTimelineViewportElement] = useState( null, @@ -1112,70 +986,18 @@ const ConversationTimeline = memo(function ConversationTimeline({ registerTimeline?.(list?.getScrollableNode() ?? null); }; - useLayoutEffect(() => { - if (!findPositionReaderRef || !timelineViewportElement) return; - const read: ThreadFindPositionReader = (query) => { - const foldRunIds = - findFoldRunIds ?? - timelineTurnFoldRunIdsByEntryId({ - timelineEntries, - latestRun, - runningRunId, - isWorking, - runlessWorkActive, - }); - const entryById = new Map(timelineEntries.map((entry) => [entry.id, entry])); - return readThreadFindPosition( - timelineViewportElement, - query, - (rowId) => { - const rowIndex = rows.findIndex((row) => row.id === rowId); - for (const row of rows.slice(Math.max(0, rowIndex))) { - if (row.kind !== "turn-fold" && row.kind !== "attempt-fold") { - const entry = entryById.get(row.kind === "assistant-meta" ? row.message.id : row.id); - if (entry && (entry.kind === "message" || entry.kind === "proposed-plan")) - return entry.id; - continue; - } - const entry = timelineEntries.find((entry) => { - const runId = - row.kind === "turn-fold" - ? foldRunIds.get(entry.id) - : entry.kind === "message" - ? entry.message.runId - : entry.kind === "proposed-plan" - ? entry.proposedPlan.runId - : null; - return ( - runId === row.runId && - (entry.kind !== "message" || entry.message.role === "assistant") && - (row.kind !== "attempt-fold" || entry.attempt?.id === row.attemptId) - ); - }); - if (entry && (entry.kind === "message" || entry.kind === "proposed-plan")) - return entry.id; - } - return undefined; - }, - contentInsetEndAdjustment, - ); - }; - findPositionReaderRef.current = read; - return () => { - if (findPositionReaderRef.current === read) findPositionReaderRef.current = null; - }; - }, [ - contentInsetEndAdjustment, - findFoldRunIds, + useThreadFindPositionReader({ findPositionReaderRef, - isWorking, + timelineViewportElement, + findFoldRunIds, + timelineEntries, latestRun, - rows, - runlessWorkActive, runningRunId, - timelineEntries, - timelineViewportElement, - ]); + isWorking, + runlessWorkActive, + rows, + contentInsetEndAdjustment, + }); useThreadFindNavigation({ listReady: findListReady, @@ -1556,89 +1378,3 @@ function AttemptFoldTimelineRow({ row }: { row: Extract ); } - -function handoffRunsSignature(runs: ReadonlyArray): string { - return runs - .map( - (run) => - `${run.id}\0${run.providerInstanceId}\0${run.ordinal}\0${run.modelSelection.instanceId}\0${run.modelSelection.model}`, - ) - .join("\n"); -} - -function useStableHandoffRuns( - runs: ReadonlyArray, -): ReadonlyArray { - const [state, setState] = useState<{ - readonly source: ReadonlyArray | null; - readonly signature: string; - readonly value: ReadonlyArray; - }>({ source: null, signature: "", value: EMPTY_TIMELINE_RUNS }); - if (state.source === runs) return state.value; - const signature = handoffRunsSignature(runs); - const value = - signature === state.signature - ? state.value - : runs.map((run) => ({ - id: run.id, - ordinal: run.ordinal, - providerInstanceId: run.providerInstanceId, - modelSelection: run.modelSelection, - })); - setState({ source: runs, signature, value }); - return value; -} - -type MessagesTimelineRowsInput = MessagesTimelineRowsProjection["input"]; - -// Expanded-id sets can be rebuilt during render; comparing them by content keeps -// the render-time state update below from repeating forever. -function sameRowsInput(left: MessagesTimelineRowsInput, right: MessagesTimelineRowsInput): boolean { - for (const key of Object.keys(right) as Array) { - const previous = left[key]; - const next = right[key]; - if (Object.is(previous, next)) continue; - if (previous instanceof Set && next instanceof Set && shallow(previous, next)) continue; - return false; - } - return Object.keys(left).length === Object.keys(right).length; -} - -/** - * Rows derived from the previous projection of the same list, so a streamed - * text update patches its message rows instead of deriving every row again. - */ -function useIncrementalTimelineRows( - identity: string, - input: MessagesTimelineRowsInput, -): MessagesTimelineRow[] { - const [state, setState] = useState(() => ({ - identity, - input, - projection: deriveMessagesTimelineRowsWithState(input), - })); - if (state.identity === identity && sameRowsInput(state.input, input)) { - return state.projection.rows; - } - const projection = deriveMessagesTimelineRowsWithState( - input, - state.identity === identity ? state.projection : null, - ); - setState({ identity, input, projection }); - return projection.rows; -} - -function useStableRows(rows: MessagesTimelineRow[], identity: string): MessagesTimelineRow[] { - const [state, setState] = useState<{ - readonly identity: string; - readonly source: MessagesTimelineRow[] | null; - readonly stable: StableMessagesTimelineRowsState; - }>({ identity, source: null, stable: { byId: new Map(), result: [] } }); - if (state.identity === identity && state.source === rows) return state.stable.result; - const stable = computeStableMessagesTimelineRows( - rows, - state.identity === identity ? state.stable : { byId: new Map(), result: [] }, - ); - setState({ identity, source: rows, stable }); - return stable.result; -} diff --git a/apps/web/src/components/chat/useThreadFindPositionReader.ts b/apps/web/src/components/chat/useThreadFindPositionReader.ts new file mode 100644 index 0000000000000..5afc9ca25488d --- /dev/null +++ b/apps/web/src/components/chat/useThreadFindPositionReader.ts @@ -0,0 +1,100 @@ +import { useLayoutEffect } from "react"; +import type { RunId } from "@t3tools/contracts"; +import type { TimelineEntry } from "../../session-logic"; +import { + timelineTurnFoldRunIdsByEntryId, + type MessagesTimelineRow, + type TimelineLatestRun, +} from "./MessagesTimeline.logic"; +import type { ThreadFindPositionReader } from "./threadFind"; +import { readThreadFindPosition } from "./threadFindPosition"; + +/** Publishes how to read the find position of the timeline's current viewport. */ +export function useThreadFindPositionReader({ + findPositionReaderRef, + timelineViewportElement, + findFoldRunIds, + timelineEntries, + latestRun, + runningRunId, + isWorking, + runlessWorkActive, + rows, + contentInsetEndAdjustment, +}: { + readonly findPositionReaderRef: React.RefObject | undefined; + readonly timelineViewportElement: HTMLDivElement | null; + readonly findFoldRunIds: ReadonlyMap | null; + readonly timelineEntries: ReadonlyArray; + readonly latestRun: TimelineLatestRun | null; + readonly runningRunId: RunId | null; + readonly isWorking: boolean; + readonly runlessWorkActive: boolean; + readonly rows: MessagesTimelineRow[]; + readonly contentInsetEndAdjustment: number; +}) { + useLayoutEffect(() => { + if (!findPositionReaderRef || !timelineViewportElement) return; + const read: ThreadFindPositionReader = (query) => { + const foldRunIds = + findFoldRunIds ?? + timelineTurnFoldRunIdsByEntryId({ + timelineEntries, + latestRun, + runningRunId, + isWorking, + runlessWorkActive, + }); + const entryById = new Map(timelineEntries.map((entry) => [entry.id, entry])); + return readThreadFindPosition( + timelineViewportElement, + query, + (rowId) => { + const rowIndex = rows.findIndex((row) => row.id === rowId); + for (const row of rows.slice(Math.max(0, rowIndex))) { + if (row.kind !== "turn-fold" && row.kind !== "attempt-fold") { + const entry = entryById.get(row.kind === "assistant-meta" ? row.message.id : row.id); + if (entry && (entry.kind === "message" || entry.kind === "proposed-plan")) + return entry.id; + continue; + } + const entry = timelineEntries.find((entry) => { + const runId = + row.kind === "turn-fold" + ? foldRunIds.get(entry.id) + : entry.kind === "message" + ? entry.message.runId + : entry.kind === "proposed-plan" + ? entry.proposedPlan.runId + : null; + return ( + runId === row.runId && + (entry.kind !== "message" || entry.message.role === "assistant") && + (row.kind !== "attempt-fold" || entry.attempt?.id === row.attemptId) + ); + }); + if (entry && (entry.kind === "message" || entry.kind === "proposed-plan")) + return entry.id; + } + return undefined; + }, + contentInsetEndAdjustment, + ); + }; + findPositionReaderRef.current = read; + return () => { + if (findPositionReaderRef.current === read) findPositionReaderRef.current = null; + }; + }, [ + contentInsetEndAdjustment, + findFoldRunIds, + findPositionReaderRef, + isWorking, + latestRun, + rows, + runlessWorkActive, + runningRunId, + timelineEntries, + timelineViewportElement, + ]); +} diff --git a/apps/web/src/components/chat/useTimelinePositionRestore.ts b/apps/web/src/components/chat/useTimelinePositionRestore.ts new file mode 100644 index 0000000000000..700a2a41af06b --- /dev/null +++ b/apps/web/src/components/chat/useTimelinePositionRestore.ts @@ -0,0 +1,166 @@ +import { useLayoutEffect, useRef } from "react"; +import type { LegendListRef } from "@legendapp/list/react"; +import type { AssistantCitationRequest } from "./AssistantCitationSource"; +import type { MessagesTimelineRow } from "./MessagesTimeline.logic"; +import type { RememberedTimelinePosition } from "./timelineScrollAnchoring"; + +/** + * Scrolls a newly shown thread back to where the user left it, or to its end, + * and reports the thread positioned once the saved row settles. + */ +export function useTimelinePositionRestore({ + listRef, + listIdentityKey, + restoringThreadPosition, + rememberedPosition, + rows, + citationRequest, + cancelPositionRestoreRef, + onManualNavigation, + onPositioned, +}: { + readonly listRef: React.RefObject; + readonly listIdentityKey: string; + readonly restoringThreadPosition: boolean; + readonly rememberedPosition: RememberedTimelinePosition | undefined; + readonly rows: MessagesTimelineRow[]; + readonly citationRequest: AssistantCitationRequest | null; + readonly cancelPositionRestoreRef: React.RefObject<(() => void) | null> | undefined; + readonly onManualNavigation: () => void; + readonly onPositioned: (identity: string) => void; +}) { + // Preserve initialScrollAtEnd's measured target until the list is reused + // for another thread; scrollToEnd would replace it with an estimated offset. + const listOpenedOnRef = useRef<{ + readonly list: LegendListRef; + identity: string | null; + } | null>(null); + useLayoutEffect(() => { + const list = listRef.current; + const opened = listOpenedOnRef.current; + if (!list) return; + if (opened?.list !== list) { + listOpenedOnRef.current = { list, identity: listIdentityKey }; + } else if (opened.identity !== listIdentityKey) { + opened.identity = null; + } + }); + useLayoutEffect(() => { + if (!restoringThreadPosition || rows.length === 0) return; + const list = listRef.current; + if (!list) return; + if (citationRequest !== null) { + // The citation scroll owns positioning once the list has mounted. + onPositioned(listIdentityKey); + return; + } + let cancelled = false; + let settleFrame: number | null = null; + const viewport: HTMLElement | null = list.getScrollableNode(); + const cancelRestoration = () => { + if (cancelled) return; + cancelled = true; + if (settleFrame !== null) cancelAnimationFrame(settleFrame); + if (viewport) void list.scrollToOffset({ offset: viewport.scrollTop, animated: false }); + onPositioned(listIdentityKey); + }; + const cancelForNavigation = () => { + cancelRestoration(); + onManualNavigation(); + }; + const onScrollKey = (event: globalThis.KeyboardEvent) => { + if ( + ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key) && + !( + event.target instanceof Element && + event.target.closest("input, textarea, [contenteditable=true]") + ) + ) + cancelForNavigation(); + }; + viewport?.addEventListener("wheel", cancelForNavigation, { passive: true }); + viewport?.addEventListener("touchmove", cancelForNavigation, { passive: true }); + viewport?.addEventListener("pointerdown", cancelForNavigation, { passive: true }); + viewport?.ownerDocument.addEventListener("keydown", onScrollKey); + const position = rememberedPosition; + const index = position ? rows.findIndex((row) => row.id === position.rowId) : -1; + if (position?.atEnd === false) onManualNavigation(); + if (cancelPositionRestoreRef) cancelPositionRestoreRef.current = cancelRestoration; + const scrolling = + position?.atEnd === false + ? index >= 0 + ? list.scrollToIndex({ + index, + animated: false, + viewPosition: 0, + viewOffset: -position.offsetWithinRow, + }) + : list.scrollToOffset({ offset: position.scrollOffset, animated: false }) + : listOpenedOnRef.current?.identity === listIdentityKey + ? undefined + : list.scrollToEnd({ animated: false }); + void Promise.resolve(scrolling).then(() => { + if (cancelled) return; + if (position?.atEnd !== false || index < 0) { + onPositioned(listIdentityKey); + return; + } + // Index scrolling starts from estimates. Keep the saved row mounted + // until its measured position and the DOM agree for two layout frames. + let stableFrames = 0; + const reconcile = () => { + if (cancelled) return; + const state = list.getState(); + const rowIndex = state.indexByKey(position.rowId); + const row = rowIndex === undefined ? undefined : state.elementAtIndex(rowIndex); + const element = list.getScrollableNode(); + if (!row || !element) return; + const offset = Math.max( + 0, + Math.min( + element.scrollTop + + row.getBoundingClientRect().top - + element.getBoundingClientRect().top + + position.offsetWithinRow, + element.scrollHeight - element.clientHeight, + ), + ); + if (Math.abs(element.scrollTop - offset) > 1) { + stableFrames = 0; + void list.scrollToOffset({ offset, animated: false }).then(() => { + if (!cancelled) settleFrame = requestAnimationFrame(reconcile); + }); + return; + } + stableFrames += 1; + if (stableFrames >= 2) { + onPositioned(listIdentityKey); + } else { + settleFrame = requestAnimationFrame(reconcile); + } + }; + settleFrame = requestAnimationFrame(reconcile); + }); + return () => { + cancelled = true; + if (cancelPositionRestoreRef?.current === cancelRestoration) { + cancelPositionRestoreRef.current = null; + } + if (settleFrame !== null) cancelAnimationFrame(settleFrame); + viewport?.removeEventListener("wheel", cancelForNavigation); + viewport?.removeEventListener("touchmove", cancelForNavigation); + viewport?.removeEventListener("pointerdown", cancelForNavigation); + viewport?.ownerDocument.removeEventListener("keydown", onScrollKey); + }; + }, [ + citationRequest, + cancelPositionRestoreRef, + listIdentityKey, + listRef, + onManualNavigation, + onPositioned, + rememberedPosition, + restoringThreadPosition, + rows, + ]); +} diff --git a/apps/web/src/components/chat/useTimelineRows.ts b/apps/web/src/components/chat/useTimelineRows.ts new file mode 100644 index 0000000000000..847dcc594af7a --- /dev/null +++ b/apps/web/src/components/chat/useTimelineRows.ts @@ -0,0 +1,78 @@ +import { useState } from "react"; +import { + computeStableMessagesTimelineRows, + handoffRunsSignature, + type MessagesTimelineRowsInput, + sameRowsInput, + deriveMessagesTimelineRowsWithState, + type StableMessagesTimelineRowsState, + type MessagesTimelineRow, +} from "./MessagesTimeline.logic"; +import { type HandoffTimelineRun } from "./V2LifecycleRow"; + +const EMPTY_TIMELINE_RUNS: ReadonlyArray = []; + +export function useStableHandoffRuns( + runs: ReadonlyArray, +): ReadonlyArray { + const [state, setState] = useState<{ + readonly source: ReadonlyArray | null; + readonly signature: string; + readonly value: ReadonlyArray; + }>({ source: null, signature: "", value: EMPTY_TIMELINE_RUNS }); + if (state.source === runs) return state.value; + const signature = handoffRunsSignature(runs); + const value = + signature === state.signature + ? state.value + : runs.map((run) => ({ + id: run.id, + ordinal: run.ordinal, + providerInstanceId: run.providerInstanceId, + modelSelection: run.modelSelection, + })); + setState({ source: runs, signature, value }); + return value; +} + +/** + * Rows derived from the previous projection of the same list, so a streamed + * text update patches its message rows instead of deriving every row again. + */ +export function useIncrementalTimelineRows( + identity: string, + input: MessagesTimelineRowsInput, +): MessagesTimelineRow[] { + const [state, setState] = useState(() => ({ + identity, + input, + projection: deriveMessagesTimelineRowsWithState(input), + })); + if (state.identity === identity && sameRowsInput(state.input, input)) { + return state.projection.rows; + } + const projection = deriveMessagesTimelineRowsWithState( + input, + state.identity === identity ? state.projection : null, + ); + setState({ identity, input, projection }); + return projection.rows; +} + +export function useStableRows( + rows: MessagesTimelineRow[], + identity: string, +): MessagesTimelineRow[] { + const [state, setState] = useState<{ + readonly identity: string; + readonly source: MessagesTimelineRow[] | null; + readonly stable: StableMessagesTimelineRowsState; + }>({ identity, source: null, stable: { byId: new Map(), result: [] } }); + if (state.identity === identity && state.source === rows) return state.stable.result; + const stable = computeStableMessagesTimelineRows( + rows, + state.identity === identity ? state.stable : { byId: new Map(), result: [] }, + ); + setState({ identity, source: rows, stable }); + return stable.result; +} From 9ff8cdca8565d8ae0cbfc22130ea65c040d97545 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:13:18 -0700 Subject: [PATCH 014/179] refactor(web): the command palette's theme items live in their own file (#22679) Co-authored-by: Claude Opus 5.5 --- apps/web/src/components/CommandPalette.tsx | 153 +--------------- .../components/CommandPaletteThemeItems.tsx | 163 ++++++++++++++++++ 2 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 apps/web/src/components/CommandPaletteThemeItems.tsx diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 055c2d76c4fd3..c44bb2c77858c 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -67,13 +67,10 @@ import { MessageSquareDashedIcon, LinkIcon, MessageSquareIcon, - MonitorIcon, - MoonIcon, PaletteIcon, RotateCcwIcon, SettingsIcon, SquarePenIcon, - SunIcon, TextSearchIcon, } from "lucide-react"; import { requestThreadFindOpen } from "./chat/threadFindActionBus"; @@ -104,15 +101,6 @@ import { import { useClientSettings } from "../hooks/useSettings"; import { useStableCallback } from "../hooks/useStableCallback"; import { useTheme } from "../hooks/useTheme"; -import { useCustomThemes } from "../hooks/useCustomThemes"; -import { useEnvironmentThemeDefinitions } from "../hooks/useEnvironmentTheme"; -import { BUILT_IN_THEMES } from "@t3tools/shared/themePalettes"; -import { getThemeDefinition } from "../themePalette"; -import { - STANDARD_THEME_CARDS, - getThemeCardDefinition, - ThemePreviewCircle, -} from "./settings/ThemePreviewCircles"; import { readLocalApi } from "../localApi"; import { desktopLocalBackendId } from "../connection/desktopLocal"; import { filesystemEnvironment, useFilesystemReadAccess } from "../state/filesystem"; @@ -203,6 +191,11 @@ import { import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic"; import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; import { CommandPaletteContent } from "./CommandPaletteContent"; +import { + APPEARANCE_OPTIONS, + notifyThemeSaveFailure, + useCommandPaletteThemeItems, +} from "./CommandPaletteThemeItems"; import { CommandPaletteTitleIcon, CommandPaletteVirtualizedResults, @@ -357,41 +350,9 @@ function overlayModeForCommand(command: string | null): SearchOverlayMode | null : null; } -const APPEARANCE_OPTIONS = [ - { mode: "system", label: "System", icon: MonitorIcon }, - { mode: "light", label: "Light", icon: SunIcon }, - { mode: "dark", label: "Dark", icon: MoonIcon }, -] as const; - -function notifyThemeSaveFailure(): void { - toastManager.add( - stackedThreadToast({ - type: "error", - title: "Couldn't save theme selection", - description: "Try again.", - }), - ); -} - // Each helper reads its own source hook so the compiler caches the derived value on that // source. Taking the source as an argument instead leaves the helper hook-free, and the // compiler then treats it as a plain function and caches nothing. -function useThemeCards() { - const customThemes = useCustomThemes(); - const environmentThemes = useEnvironmentThemeDefinitions(); - const seen = new Set(); - return [ - ...STANDARD_THEME_CARDS.map((card) => ({ ...card, id: null })), - ...[...BUILT_IN_THEMES, ...customThemes, ...environmentThemes] - .filter((definition) => { - if (seen.has(definition.id)) return false; - seen.add(definition.id); - return true; - }) - .map(getThemeCardDefinition), - ]; -} - function useThreadSearchWithContentMatches( environmentIds: ReadonlyArray, query: string, @@ -817,16 +778,7 @@ function OpenCommandPaletteDialog(props: { }; const threads = useThreadShells(); const keybindings = useAtomValue(primaryServerKeybindingsAtom); - const { - theme, - themeHalves, - resolvedTheme, - appearanceMode, - setAppearanceMode, - setTheme, - setThemeHalf, - } = useTheme(); - const themeCards = useThemeCards(); + const { changeThemeItem, changeAppearanceItem, themeEditorItem } = useCommandPaletteThemeItems(); const providers = useAtomValue(primaryServerProvidersAtom); const providersByEnvironment = useEnvironmentProviders(); const providerEntryByEnvironmentAndInstanceId = new Map( @@ -1863,84 +1815,7 @@ function OpenCommandPaletteDialog(props: { }); } - const changeThemeItem: CommandPaletteSubmenuItem = { - kind: "submenu", - value: "action:change-theme", - searchTerms: ["change theme", "appearance", "colors", "palette"], - title: "Change theme", - icon: , - addonIcon: , - shortcutCommand: "theme.select", - groups: [ - { - value: "themes", - label: "Change theme", - items: themeCards.map(({ id, label, previews }) => ({ - kind: "action", - value: id === null ? "theme:standard" : `theme:palette:${id}`, - title: label, - description: previews.length === 1 ? `For ${previews[0]!.mode} mode` : undefined, - searchTerms: [label, "theme", "appearance"], - icon: , - titleTrailingContent: ( - - {(themeHalves?.[resolvedTheme] ?? getThemeDefinition(theme)?.id ?? null) === id ? ( - Current - ) : null} - - {previews.map((preview) => ( - - ))} - - - ), - run: async () => { - const saved = - previews.length === 1 && id !== null - ? setThemeHalf(previews[0]!.mode, id) - : setTheme(id ?? appearanceMode); - if (!saved) notifyThemeSaveFailure(); - }, - })), - }, - ], - }; actionItems.push(changeThemeItem); - - const changeAppearanceItem: CommandPaletteSubmenuItem = { - kind: "submenu", - value: "action:change-appearance", - searchTerms: ["change appearance", "light", "dark", "system", "mode", "toggle"], - title: "Change appearance", - icon: , - addonIcon: , - shortcutCommand: "appearance.cycle", - groups: [ - { - value: "appearance", - label: "Change appearance", - items: APPEARANCE_OPTIONS.map(({ mode, label, icon: Icon }) => ({ - kind: "action", - value: `appearance:${mode}`, - title: label, - searchTerms: [label, "appearance", "mode"], - icon: , - titleTrailingContent: - appearanceMode === mode ? ( - Current - ) : undefined, - run: async () => { - if (!setAppearanceMode(mode)) notifyThemeSaveFailure(); - }, - })), - }, - ], - }; actionItems.push(changeAppearanceItem); // The parent pushes an intent into this mounted dialog; applying it clears the @@ -1984,21 +1859,7 @@ function OpenCommandPaletteDialog(props: { applyOpenIntent(openIntent); }, [openIntent, projectThreadItemCount]); - actionItems.push({ - kind: "action", - value: "action:theme-editor", - searchTerms: ["theme", "appearance", "colors", "palette", "customize"], - title: "Toggle theme editor", - icon: , - shortcutCommand: "themeEditor.toggle", - run: async () => { - toggleThemeEditorForTheme({ - theme, - themeHalves, - initialAppearance: resolvedTheme, - }); - }, - }); + actionItems.push(themeEditorItem); if ( environments.some( diff --git a/apps/web/src/components/CommandPaletteThemeItems.tsx b/apps/web/src/components/CommandPaletteThemeItems.tsx new file mode 100644 index 0000000000000..818229d950668 --- /dev/null +++ b/apps/web/src/components/CommandPaletteThemeItems.tsx @@ -0,0 +1,163 @@ +import { BUILT_IN_THEMES } from "@t3tools/shared/themePalettes"; +import { MonitorIcon, MoonIcon, PaletteIcon, SunIcon } from "lucide-react"; + +import { useCustomThemes } from "../hooks/useCustomThemes"; +import { useEnvironmentThemeDefinitions } from "../hooks/useEnvironmentTheme"; +import { useTheme } from "../hooks/useTheme"; +import { getThemeDefinition } from "../themePalette"; +import { + ADDON_ICON_CLASS, + ITEM_ICON_CLASS, + type CommandPaletteActionItem, + type CommandPaletteSubmenuItem, +} from "./CommandPalette.logic"; +import { toggleThemeEditorForTheme } from "./settings/themeEditorStore"; +import { + STANDARD_THEME_CARDS, + getThemeCardDefinition, + ThemePreviewCircle, +} from "./settings/ThemePreviewCircles"; +import { stackedThreadToast, toastManager } from "./ui/toast"; + +export const APPEARANCE_OPTIONS = [ + { mode: "system", label: "System", icon: MonitorIcon }, + { mode: "light", label: "Light", icon: SunIcon }, + { mode: "dark", label: "Dark", icon: MoonIcon }, +] as const; + +export function notifyThemeSaveFailure(): void { + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Couldn't save theme selection", + description: "Try again.", + }), + ); +} + +// Reads its own source hooks so the compiler caches the cards on them. Taking the +// sources as arguments would leave it hook-free, and the compiler would cache nothing. +function useThemeCards() { + const customThemes = useCustomThemes(); + const environmentThemes = useEnvironmentThemeDefinitions(); + const seen = new Set(); + return [ + ...STANDARD_THEME_CARDS.map((card) => ({ ...card, id: null })), + ...[...BUILT_IN_THEMES, ...customThemes, ...environmentThemes] + .filter((definition) => { + if (seen.has(definition.id)) return false; + seen.add(definition.id); + return true; + }) + .map(getThemeCardDefinition), + ]; +} + +export function useCommandPaletteThemeItems() { + const { + theme, + themeHalves, + resolvedTheme, + appearanceMode, + setAppearanceMode, + setTheme, + setThemeHalf, + } = useTheme(); + const themeCards = useThemeCards(); + + const changeThemeItem: CommandPaletteSubmenuItem = { + kind: "submenu", + value: "action:change-theme", + searchTerms: ["change theme", "appearance", "colors", "palette"], + title: "Change theme", + icon: , + addonIcon: , + shortcutCommand: "theme.select", + groups: [ + { + value: "themes", + label: "Change theme", + items: themeCards.map(({ id, label, previews }) => ({ + kind: "action", + value: id === null ? "theme:standard" : `theme:palette:${id}`, + title: label, + description: previews.length === 1 ? `For ${previews[0]!.mode} mode` : undefined, + searchTerms: [label, "theme", "appearance"], + icon: , + titleTrailingContent: ( + + {(themeHalves?.[resolvedTheme] ?? getThemeDefinition(theme)?.id ?? null) === id ? ( + Current + ) : null} + + {previews.map((preview) => ( + + ))} + + + ), + run: async () => { + const saved = + previews.length === 1 && id !== null + ? setThemeHalf(previews[0]!.mode, id) + : setTheme(id ?? appearanceMode); + if (!saved) notifyThemeSaveFailure(); + }, + })), + }, + ], + }; + + const changeAppearanceItem: CommandPaletteSubmenuItem = { + kind: "submenu", + value: "action:change-appearance", + searchTerms: ["change appearance", "light", "dark", "system", "mode", "toggle"], + title: "Change appearance", + icon: , + addonIcon: , + shortcutCommand: "appearance.cycle", + groups: [ + { + value: "appearance", + label: "Change appearance", + items: APPEARANCE_OPTIONS.map(({ mode, label, icon: Icon }) => ({ + kind: "action", + value: `appearance:${mode}`, + title: label, + searchTerms: [label, "appearance", "mode"], + icon: , + titleTrailingContent: + appearanceMode === mode ? ( + Current + ) : undefined, + run: async () => { + if (!setAppearanceMode(mode)) notifyThemeSaveFailure(); + }, + })), + }, + ], + }; + + const themeEditorItem: CommandPaletteActionItem = { + kind: "action", + value: "action:theme-editor", + searchTerms: ["theme", "appearance", "colors", "palette", "customize"], + title: "Toggle theme editor", + icon: , + shortcutCommand: "themeEditor.toggle", + run: async () => { + toggleThemeEditorForTheme({ + theme, + themeHalves, + initialAppearance: resolvedTheme, + }); + }, + }; + + return { changeThemeItem, changeAppearanceItem, themeEditorItem }; +} From 767134a4a7a64d8d5dd29e84423157f94e28064e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:13:19 -0700 Subject: [PATCH 015/179] refactor(web): the command palette's thread items come from their own hook (#22687) Co-authored-by: Claude Opus 5.5 --- apps/web/src/components/CommandPalette.tsx | 222 ++---------------- .../src/components/CommandPaletteLookups.ts | 93 ++++++++ .../components/CommandPaletteThreadItems.tsx | 150 ++++++++++++ 3 files changed, 257 insertions(+), 208 deletions(-) create mode 100644 apps/web/src/components/CommandPaletteLookups.ts create mode 100644 apps/web/src/components/CommandPaletteThreadItems.tsx diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index c44bb2c77858c..4f434fce7a084 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -24,7 +24,6 @@ import { } from "@t3tools/client-runtime/operations/projects"; import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients"; import { connectionStatusText } from "@t3tools/client-runtime/connection"; -import { threadSearchMatchKey } from "@t3tools/client-runtime/state/thread-search"; import { resolveThreadReferenceCopyTarget } from "@t3tools/shared/threadReference"; import { canPreloadBrowsePath, @@ -42,11 +41,9 @@ import { AuthSourceControlWriteScope, AuthFilesystemReadScope, type DesktopWslState, - type ClientSettings, type EnvironmentId, type EnvironmentMachineKind, type FilesystemBrowseResult, - type ProjectId, type SourceControlDiscoveryResult, SourceControlProviderKind, type SourceControlRepositoryInfo, @@ -121,7 +118,6 @@ import { usePrimaryEnvironmentId, } from "../state/environments"; import { useProjects, useServerConfigs, useThreadShells, waitForProject } from "../state/entities"; -import { useThreadSearch } from "../state/queries"; import { resolveThreadActionProjectRef, startNewThreadFromContext } from "../lib/chatThreadActions"; import { appendBrowsePathSegment, @@ -166,7 +162,6 @@ import { buildCommandPaletteProjectMetadata, buildProjectActionItems, buildRootGroups, - buildThreadActionItems, buildLinkedThreadActionItems, buildCommandPaletteRows, enumerateCommandPaletteItems, @@ -188,9 +183,15 @@ import { reduceCommandPaletteSearchState, type SearchOverlayMode, } from "./CommandPalette.logic"; -import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic"; import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; import { CommandPaletteContent } from "./CommandPaletteContent"; +import { + selectCommandPaletteClientSettings, + useEnvironmentLookups, + useProjectLookups, + useSortedProjectGroups, +} from "./CommandPaletteLookups"; +import { useCommandPaletteThreadItems } from "./CommandPaletteThreadItems"; import { APPEARANCE_OPTIONS, notifyThemeSaveFailure, @@ -210,22 +211,8 @@ import { openLinkPullRequestDialog } from "./pullRequest/LinkPullRequestDialog"; import { ProjectContentSearchDialog } from "./search/ProjectContentSearchDialog"; import { toggleThemeEditorForTheme } from "./settings/themeEditorStore"; import { searchSettings, SETTINGS_SECTION_LABELS } from "./settings/settingsSearch"; -import { - COMMAND_PALETTE_META_ICON_CLASS, - CommandPaletteMetaDot, - ThreadCommandSubtitle, -} from "./ThreadCommandSubtitle"; -import { - ThreadRowLeadingStatus, - ThreadRowTrailingStatus, - ThreadSearchPullRequestNumber, -} from "./ThreadStatusIndicators"; -import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server"; -import { - applyProviderInstanceSettings, - deriveProviderInstanceEntries, - type ProviderInstanceEntry, -} from "../providerInstances"; +import { COMMAND_PALETTE_META_ICON_CLASS, CommandPaletteMetaDot } from "./ThreadCommandSubtitle"; +import { primaryServerKeybindingsAtom } from "../state/server"; import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings"; import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command"; import { Button } from "./ui/button"; @@ -235,12 +222,7 @@ import { stackedThreadToast, toastManager } from "./ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; -import { getProjectOrderKey } from "../logicalProject"; -import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; -import { - buildSidebarProjectPickerEntries, - buildSidebarProjectSnapshots, -} from "../sidebarProjectGrouping"; +import { buildSidebarProjectPickerEntries } from "../sidebarProjectGrouping"; import type { Project } from "../types"; import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons"; import { readPullRequestListPreferences } from "~/components/pullRequest/pullRequestListPreferences"; @@ -350,99 +332,6 @@ function overlayModeForCommand(command: string | null): SearchOverlayMode | null : null; } -// Each helper reads its own source hook so the compiler caches the derived value on that -// source. Taking the source as an argument instead leaves the helper hook-free, and the -// compiler then treats it as a plain function and caches nothing. -function useThreadSearchWithContentMatches( - environmentIds: ReadonlyArray, - query: string, -) { - const threadSearch = useThreadSearch(environmentIds, query); - const contentMatchByKey = new Map( - threadSearch.matches.flatMap((match) => - match.source === "user" || match.source === "assistant" - ? [[threadSearchMatchKey(match), match] as const] - : [], - ), - ); - return { threadSearch, contentMatchByKey }; -} - -function useEnvironmentLookups() { - const { environments } = useEnvironments(); - const environmentLabelById = new Map( - environments.map((environment) => [environment.environmentId, environment.label] as const), - ); - const projectEnvironmentLocationById = new Map( - environments.map((environment) => { - const isPrimary = environment.entry.target._tag === "PrimaryConnectionTarget"; - const isLocal = isPrimary || isDesktopLocalConnectionTarget(environment.entry.target); - return [ - environment.environmentId, - { - kind: isLocal ? "local" : "remote", - label: isPrimary ? "Local" : isLocal ? `${environment.label} (Local)` : environment.label, - machine: resolveEnvironmentMachineKind(environment.serverConfig), - }, - ] as const; - }), - ); - return { environmentLabelById, projectEnvironmentLocationById }; -} - -function useProjectLookups() { - const projects = useProjects(); - const projectCwdById = new Map( - projects.map((project) => [project.id, project.workspaceRoot]), - ); - const projectByKey = new Map( - projects.map((project) => [`${project.environmentId}:${project.id}`, project]), - ); - const projectTitleById = new Map( - projects.map((project) => [project.id, project.title]), - ); - return { projectCwdById, projectByKey, projectTitleById }; -} - -const selectCommandPaletteClientSettings = (settings: ClientSettings) => ({ - sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, - sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, - sidebarProjectSortOrder: settings.sidebarProjectSortOrder, - sidebarThreadSortOrder: settings.sidebarThreadSortOrder, -}); - -function useSortedProjectGroups(environmentLabelById: ReadonlyMap) { - const projects = useProjects(); - const threads = useThreadShells(); - const projectOrder = useUiStateStore((store) => store.projectOrder); - const clientSettings = useClientSettings(selectCommandPaletteClientSettings); - const primaryEnvironmentId = usePrimaryEnvironmentId(); - const projectGroupingSettings = { - sidebarProjectGroupingMode: clientSettings.sidebarProjectGroupingMode, - sidebarProjectGroupingOverrides: clientSettings.sidebarProjectGroupingOverrides, - }; - const orderedProjects = orderItemsByPreferredIds({ - items: projects, - preferredIds: projectOrder, - getId: getProjectOrderKey, - getPreferenceIds: (project) => [ - getProjectOrderKey(project), - legacyProjectCwdPreferenceKey(project.workspaceRoot), - ], - }); - const unsortedProjectGroups = buildSidebarProjectSnapshots({ - projects: clientSettings.sidebarProjectSortOrder === "manual" ? orderedProjects : projects, - settings: projectGroupingSettings, - primaryEnvironmentId, - resolveEnvironmentLabel: (environmentId) => environmentLabelById.get(environmentId) ?? null, - }); - return sortLogicalProjectsForSidebar( - unsortedProjectGroups, - threads, - clientSettings.sidebarProjectSortOrder, - ); -} - export function CommandPalette({ children }: { children: ReactNode }) { const navigate = useNavigate(); const [state, dispatch] = useReducer(reduceCommandPaletteUiState, { @@ -778,31 +667,10 @@ function OpenCommandPaletteDialog(props: { }; const threads = useThreadShells(); const keybindings = useAtomValue(primaryServerKeybindingsAtom); - const { changeThemeItem, changeAppearanceItem, themeEditorItem } = useCommandPaletteThemeItems(); - const providers = useAtomValue(primaryServerProvidersAtom); const providersByEnvironment = useEnvironmentProviders(); - const providerEntryByEnvironmentAndInstanceId = new Map( - environments.flatMap((environment) => { - const serverConfig = environment.serverConfig; - const derived = deriveProviderInstanceEntries( - providersByEnvironment.get(environment.environmentId) ?? - (environment.environmentId === primaryEnvironmentId ? providers : []), - ); - const entries = serverConfig - ? applyProviderInstanceSettings(derived, serverConfig.settings, null) - : derived; - return entries.map( - (entry) => [`${environment.environmentId}:${entry.instanceId}`, entry] as const, - ); - }), - ); + const { changeThemeItem, changeAppearanceItem, themeEditorItem } = useCommandPaletteThemeItems(); const currentView = viewStack.at(-1)?.view ?? null; - const environmentIds = environments - .filter((environment) => environment.connection.phase === "connected") - .map((environment) => environment.environmentId); const threadSearchQuery = currentView === null && !isActionsOnly ? deferredQuery : ""; - const { threadSearch, contentMatchByKey: threadContentMatchByKey } = - useThreadSearchWithContentMatches(environmentIds, threadSearchQuery); const [browseGeneration, setBrowseGeneration] = useState(0); const [browseNavigation] = useState(createBrowseNavigationCoordinator); const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState( @@ -1183,70 +1051,8 @@ function OpenCommandPaletteDialog(props: { ]; })(); - const allThreadItems = buildThreadActionItems({ - threads, - ...(activeThreadId ? { activeThreadId } : {}), - projectTitleById, - sortOrder: clientSettings.sidebarThreadSortOrder, - icon: , - renderLeadingContent: (thread) => , - renderTrailingContent: (thread) => ( - <> - - - - ), - renderDescription: (thread, { projectTitle }) => { - const modelInstanceId = - thread.runtime?.providerInstanceId ?? thread.modelSelection.instanceId; - const providerEntry = - providerEntryByEnvironmentAndInstanceId.get(`${thread.environmentId}:${modelInstanceId}`) ?? - null; - return ( - - ); - }, - getContentMatch: (thread) => { - const match = threadContentMatchByKey.get( - threadSearchMatchKey({ - environmentId: thread.environmentId, - threadId: thread.id, - }), - ); - return match && (match.source === "user" || match.source === "assistant") - ? { - source: match.source, - snippet: match.snippet, - query: threadSearch.query, - } - : undefined; - }, - runThread: async (thread) => { - await navigate({ - to: "/$environmentId/$threadId", - params: buildThreadRouteParams(scopeThreadRef(thread.environmentId, thread.id)), - }); - }, - }); + const { threadItems: allThreadItems, isSearchPending: isThreadSearchPending } = + useCommandPaletteThreadItems({ activeThreadId, threadSearchQuery }); const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); // Stable so the item builders that capture it during render never see the @@ -3208,7 +3014,7 @@ function OpenCommandPaletteDialog(props: { ? { emptyStateMessage: "Press Enter to create this folder and add it as a project.", } - : threadSearch.isPending + : isThreadSearchPending ? { emptyStateMessage: "Searching thread messages…" } : {})} /> diff --git a/apps/web/src/components/CommandPaletteLookups.ts b/apps/web/src/components/CommandPaletteLookups.ts new file mode 100644 index 0000000000000..2b6d6497c0cb4 --- /dev/null +++ b/apps/web/src/components/CommandPaletteLookups.ts @@ -0,0 +1,93 @@ +import { + type ClientSettings, + type EnvironmentId, + type ProjectId, + resolveEnvironmentMachineKind, +} from "@t3tools/contracts"; + +import { isDesktopLocalConnectionTarget } from "../connection/desktopLocal"; +import { useClientSettings } from "../hooks/useSettings"; +import { getProjectOrderKey } from "../logicalProject"; +import { useProjects, useThreadShells } from "../state/entities"; +import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; +import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping"; +import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; +import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic"; + +// Each helper reads its own source hook so the compiler caches the derived value on that +// source. Taking the source as an argument instead leaves the helper hook-free, and the +// compiler then treats it as a plain function and caches nothing. +export function useEnvironmentLookups() { + const { environments } = useEnvironments(); + const environmentLabelById = new Map( + environments.map((environment) => [environment.environmentId, environment.label] as const), + ); + const projectEnvironmentLocationById = new Map( + environments.map((environment) => { + const isPrimary = environment.entry.target._tag === "PrimaryConnectionTarget"; + const isLocal = isPrimary || isDesktopLocalConnectionTarget(environment.entry.target); + return [ + environment.environmentId, + { + kind: isLocal ? "local" : "remote", + label: isPrimary ? "Local" : isLocal ? `${environment.label} (Local)` : environment.label, + machine: resolveEnvironmentMachineKind(environment.serverConfig), + }, + ] as const; + }), + ); + return { environmentLabelById, projectEnvironmentLocationById }; +} + +export function useProjectLookups() { + const projects = useProjects(); + const projectCwdById = new Map( + projects.map((project) => [project.id, project.workspaceRoot]), + ); + const projectByKey = new Map( + projects.map((project) => [`${project.environmentId}:${project.id}`, project]), + ); + const projectTitleById = new Map( + projects.map((project) => [project.id, project.title]), + ); + return { projectCwdById, projectByKey, projectTitleById }; +} + +export const selectCommandPaletteClientSettings = (settings: ClientSettings) => ({ + sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, + sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, + sidebarProjectSortOrder: settings.sidebarProjectSortOrder, + sidebarThreadSortOrder: settings.sidebarThreadSortOrder, +}); + +export function useSortedProjectGroups(environmentLabelById: ReadonlyMap) { + const projects = useProjects(); + const threads = useThreadShells(); + const projectOrder = useUiStateStore((store) => store.projectOrder); + const clientSettings = useClientSettings(selectCommandPaletteClientSettings); + const primaryEnvironmentId = usePrimaryEnvironmentId(); + const projectGroupingSettings = { + sidebarProjectGroupingMode: clientSettings.sidebarProjectGroupingMode, + sidebarProjectGroupingOverrides: clientSettings.sidebarProjectGroupingOverrides, + }; + const orderedProjects = orderItemsByPreferredIds({ + items: projects, + preferredIds: projectOrder, + getId: getProjectOrderKey, + getPreferenceIds: (project) => [ + getProjectOrderKey(project), + legacyProjectCwdPreferenceKey(project.workspaceRoot), + ], + }); + const unsortedProjectGroups = buildSidebarProjectSnapshots({ + projects: clientSettings.sidebarProjectSortOrder === "manual" ? orderedProjects : projects, + settings: projectGroupingSettings, + primaryEnvironmentId, + resolveEnvironmentLabel: (environmentId) => environmentLabelById.get(environmentId) ?? null, + }); + return sortLogicalProjectsForSidebar( + unsortedProjectGroups, + threads, + clientSettings.sidebarProjectSortOrder, + ); +} diff --git a/apps/web/src/components/CommandPaletteThreadItems.tsx b/apps/web/src/components/CommandPaletteThreadItems.tsx new file mode 100644 index 0000000000000..e2732f5419082 --- /dev/null +++ b/apps/web/src/components/CommandPaletteThreadItems.tsx @@ -0,0 +1,150 @@ +import { scopeThreadRef } from "@t3tools/client-runtime/environment"; +import { threadSearchMatchKey } from "@t3tools/client-runtime/state/thread-search"; +import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import { useAtomValue } from "@effect/atom-react"; +import { useNavigate } from "@tanstack/react-router"; +import { MessageSquareIcon } from "lucide-react"; + +import { useClientSettings } from "../hooks/useSettings"; +import { + applyProviderInstanceSettings, + deriveProviderInstanceEntries, + type ProviderInstanceEntry, +} from "../providerInstances"; +import { useThreadShells } from "../state/entities"; +import { + useEnvironmentProviders, + useEnvironments, + usePrimaryEnvironmentId, +} from "../state/environments"; +import { useThreadSearch } from "../state/queries"; +import { primaryServerProvidersAtom } from "../state/server"; +import { buildThreadRouteParams } from "../threadRoutes"; +import { buildThreadActionItems, ITEM_ICON_CLASS } from "./CommandPalette.logic"; +import { useEnvironmentLookups, useProjectLookups } from "./CommandPaletteLookups"; +import { ThreadCommandSubtitle } from "./ThreadCommandSubtitle"; +import { + ThreadRowLeadingStatus, + ThreadRowTrailingStatus, + ThreadSearchPullRequestNumber, +} from "./ThreadStatusIndicators"; + +function useProviderEntryByEnvironmentAndInstanceId() { + const { environments } = useEnvironments(); + const primaryEnvironmentId = usePrimaryEnvironmentId(); + const providers = useAtomValue(primaryServerProvidersAtom); + const providersByEnvironment = useEnvironmentProviders(); + return new Map( + environments.flatMap((environment) => { + const serverConfig = environment.serverConfig; + const derived = deriveProviderInstanceEntries( + providersByEnvironment.get(environment.environmentId) ?? + (environment.environmentId === primaryEnvironmentId ? providers : []), + ); + const entries = serverConfig + ? applyProviderInstanceSettings(derived, serverConfig.settings, null) + : derived; + return entries.map( + (entry) => [`${environment.environmentId}:${entry.instanceId}`, entry] as const, + ); + }), + ); +} + +function useThreadSearchWithContentMatches(query: string) { + const { environments } = useEnvironments(); + const environmentIds: ReadonlyArray = environments + .filter((environment) => environment.connection.phase === "connected") + .map((environment) => environment.environmentId); + const threadSearch = useThreadSearch(environmentIds, query); + const contentMatchByKey = new Map( + threadSearch.matches.flatMap((match) => + match.source === "user" || match.source === "assistant" + ? [[threadSearchMatchKey(match), match] as const] + : [], + ), + ); + return { threadSearch, contentMatchByKey }; +} + +/** Every thread as a palette item, with message matches for a non-empty `threadSearchQuery`. */ +export function useCommandPaletteThreadItems(input: { + readonly activeThreadId: ThreadId | undefined; + readonly threadSearchQuery: string; +}) { + const { activeThreadId, threadSearchQuery } = input; + const navigate = useNavigate(); + const threads = useThreadShells(); + const sidebarThreadSortOrder = useClientSettings((settings) => settings.sidebarThreadSortOrder); + const { projectEnvironmentLocationById } = useEnvironmentLookups(); + const providerEntryByEnvironmentAndInstanceId = useProviderEntryByEnvironmentAndInstanceId(); + const { threadSearch, contentMatchByKey } = useThreadSearchWithContentMatches(threadSearchQuery); + const { projectByKey, projectTitleById } = useProjectLookups(); + + const threadItems = buildThreadActionItems({ + threads, + ...(activeThreadId ? { activeThreadId } : {}), + projectTitleById, + sortOrder: sidebarThreadSortOrder, + icon: , + renderLeadingContent: (thread) => , + renderTrailingContent: (thread) => ( + <> + + + + ), + renderDescription: (thread, { projectTitle }) => { + const modelInstanceId = + thread.runtime?.providerInstanceId ?? thread.modelSelection.instanceId; + const providerEntry = + providerEntryByEnvironmentAndInstanceId.get(`${thread.environmentId}:${modelInstanceId}`) ?? + null; + return ( + + ); + }, + getContentMatch: (thread) => { + const match = contentMatchByKey.get( + threadSearchMatchKey({ + environmentId: thread.environmentId, + threadId: thread.id, + }), + ); + return match && (match.source === "user" || match.source === "assistant") + ? { + source: match.source, + snippet: match.snippet, + query: threadSearch.query, + } + : undefined; + }, + runThread: async (thread) => { + await navigate({ + to: "/$environmentId/$threadId", + params: buildThreadRouteParams(scopeThreadRef(thread.environmentId, thread.id)), + }); + }, + }); + + return { threadItems, isSearchPending: threadSearch.isPending }; +} From 0b7c902df77db32f575c12a9ffc1579b27513fee Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:13:19 -0700 Subject: [PATCH 016/179] refactor(web): the command palette's project items and pure helpers live in their own files (#22699) Co-authored-by: Claude Opus 5.5 --- .../src/components/CommandPalette.logic.ts | 94 ++++++++ apps/web/src/components/CommandPalette.tsx | 213 ++---------------- .../components/CommandPaletteProjectItems.tsx | 117 ++++++++++ 3 files changed, 228 insertions(+), 196 deletions(-) create mode 100644 apps/web/src/components/CommandPaletteProjectItems.tsx diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index 3037ef72159df..6fe9b7cfb6939 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -1,9 +1,19 @@ import { threadPullRequestSearchTerms } from "@t3tools/shared/threadPullRequests"; import type { CommandPaletteLinkedThreads } from "../commandPaletteBus"; +import { + addProjectRemoteSourceLabel, + addProjectRemoteSourcePathHint, + buildAddProjectRemoteSourceReadiness, + type AddProjectRemoteSource, + type AddProjectRemoteSourceReadiness, +} from "@t3tools/client-runtime/operations/projects"; import { type EnvironmentId, + type EnvironmentMachineKind, type FilesystemBrowseEntry, type KeybindingCommand, + type SourceControlDiscoveryResult, + type SourceControlRepositoryInfo, THREAD_JUMP_KEYBINDING_COMMANDS, } from "@t3tools/contracts"; import { filterFilesystemBrowseEntries } from "@t3tools/client-runtime/state/filesystem"; @@ -657,3 +667,87 @@ export function getCommandPaletteInputPlaceholder(mode: CommandPaletteMode): str return "Enter path (e.g. ~/projects/my-app)"; } } + +export function getEnvironmentBrowsePlatform(os: string | null | undefined): string { + if (os === "windows") { + return "Win32"; + } + if (os === "darwin") { + return "MacIntel"; + } + if (os === "linux") { + return "Linux"; + } + return typeof navigator === "undefined" ? "" : navigator.platform; +} + +export interface AddProjectEnvironmentOption { + readonly environmentId: EnvironmentId; + readonly label: string; + readonly machine: EnvironmentMachineKind; + readonly isPrimary: boolean; + readonly isConnected: boolean; + readonly status: string; +} + +export type AddProjectCloneFlow = + | { + readonly step: "repository"; + readonly environmentId: EnvironmentId; + readonly source: AddProjectRemoteSource; + } + | { + readonly step: "confirm"; + readonly environmentId: EnvironmentId; + readonly source: AddProjectRemoteSource; + readonly repositoryInput: string; + readonly repository: SourceControlRepositoryInfo | null; + readonly remoteUrl: string; + }; + +export function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { + if (!flow) return null; + if (flow.step === "confirm") return null; + if (flow.source === "url") { + return "Enter Git clone URL"; + } + return `Enter ${addProjectRemoteSourceLabel(flow.source)} repository (${addProjectRemoteSourcePathHint(flow.source)})`; +} + +export function buildPaletteRemoteSourceReadiness( + discovery: SourceControlDiscoveryResult | null, +): AddProjectRemoteSourceReadiness { + const readiness = buildAddProjectRemoteSourceReadiness(discovery); + return (source) => { + const entry = readiness(source); + return entry.hint === null + ? entry + : { + ...entry, + hint: entry.hint.replace( + "Open Source Control settings", + "Open Settings -> Source Control", + ), + }; + }; +} + +export function errorMessage(error: unknown): string { + if (error instanceof Error && error.message.trim().length > 0) { + return error.message; + } + return "An error occurred."; +} + +const OVERLAY_MODE_BY_COMMAND = { + "commandPalette.toggle": "command", + "filePicker.toggle": "files", + "projectSearch.toggle": "content", +} as const satisfies Partial>; + +export function overlayModeForCommand(command: string | null): SearchOverlayMode | null { + if (command === null) return null; + return command in OVERLAY_MODE_BY_COMMAND + ? OVERLAY_MODE_BY_COMMAND[command as keyof typeof OVERLAY_MODE_BY_COMMAND] + : null; +} diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 4f434fce7a084..99037bffdcba2 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -17,7 +17,6 @@ import { addProjectRemoteSourceLabel, addProjectRemoteSourcePathHint, addProjectRemoteSourceProvider, - buildAddProjectRemoteSourceReadiness, sortAddProjectProviderSources, type AddProjectRemoteSource, type AddProjectRemoteSourceReadiness, @@ -42,11 +41,8 @@ import { AuthFilesystemReadScope, type DesktopWslState, type EnvironmentId, - type EnvironmentMachineKind, type FilesystemBrowseResult, - type SourceControlDiscoveryResult, SourceControlProviderKind, - type SourceControlRepositoryInfo, PRIMARY_LOCAL_ENVIRONMENT_ID, resolveEnvironmentMachineKind, } from "@t3tools/contracts"; @@ -159,8 +155,6 @@ import { ADDON_ICON_CLASS, browseInputEndPaddingClass, buildBrowseGroups, - buildCommandPaletteProjectMetadata, - buildProjectActionItems, buildRootGroups, buildLinkedThreadActionItems, buildCommandPaletteRows, @@ -180,6 +174,13 @@ import { RECENT_THREAD_LIMIT, reduceCommandPaletteUiState, createCommandPaletteSearchState, + type AddProjectCloneFlow, + type AddProjectEnvironmentOption, + buildPaletteRemoteSourceReadiness, + errorMessage, + getEnvironmentBrowsePlatform, + overlayModeForCommand, + remoteProjectInputPlaceholder, reduceCommandPaletteSearchState, type SearchOverlayMode, } from "./CommandPalette.logic"; @@ -191,6 +192,10 @@ import { useProjectLookups, useSortedProjectGroups, } from "./CommandPaletteLookups"; +import { + buildNewThreadInProjectItems, + buildProjectSearchItems, +} from "./CommandPaletteProjectItems"; import { useCommandPaletteThreadItems } from "./CommandPaletteThreadItems"; import { APPEARANCE_OPTIONS, @@ -205,13 +210,11 @@ import { import { sourceControlIcon } from "~/sourceControlPresentation"; import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; import { Checkbox } from "./ui/checkbox"; -import { ProjectFavicon } from "./ProjectFavicon"; import { ProjectFilePicker } from "./files/ProjectFilePicker"; import { openLinkPullRequestDialog } from "./pullRequest/LinkPullRequestDialog"; import { ProjectContentSearchDialog } from "./search/ProjectContentSearchDialog"; import { toggleThemeEditorForTheme } from "./settings/themeEditorStore"; import { searchSettings, SETTINGS_SECTION_LABELS } from "./settings/settingsSearch"; -import { COMMAND_PALETTE_META_ICON_CLASS, CommandPaletteMetaDot } from "./ThreadCommandSubtitle"; import { primaryServerKeybindingsAtom } from "../state/server"; import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings"; import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command"; @@ -223,50 +226,12 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; import { buildSidebarProjectPickerEntries } from "../sidebarProjectGrouping"; -import type { Project } from "../types"; import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons"; import { readPullRequestListPreferences } from "~/components/pullRequest/pullRequestListPreferences"; import { useThreadRouteTarget } from "../hooks/useThreadRouteTarget"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; -function getEnvironmentBrowsePlatform(os: string | null | undefined): string { - if (os === "windows") { - return "Win32"; - } - if (os === "darwin") { - return "MacIntel"; - } - if (os === "linux") { - return "Linux"; - } - return typeof navigator === "undefined" ? "" : navigator.platform; -} - -interface AddProjectEnvironmentOption { - readonly environmentId: EnvironmentId; - readonly label: string; - readonly machine: EnvironmentMachineKind; - readonly isPrimary: boolean; - readonly isConnected: boolean; - readonly status: string; -} - -type AddProjectCloneFlow = - | { - readonly step: "repository"; - readonly environmentId: EnvironmentId; - readonly source: AddProjectRemoteSource; - } - | { - readonly step: "confirm"; - readonly environmentId: EnvironmentId; - readonly source: AddProjectRemoteSource; - readonly repositoryInput: string; - readonly repository: SourceControlRepositoryInfo | null; - readonly remoteUrl: string; - }; - function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { if (source === "url") return ; const Icon = sourceControlIcon(sourceControlClients.get(source)); @@ -281,57 +246,6 @@ function sourceControlHostIcon( return ; } -function projectFaviconIcon(project: Project): ReactNode { - return ; -} - -function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { - if (!flow) return null; - if (flow.step === "confirm") return null; - if (flow.source === "url") { - return "Enter Git clone URL"; - } - return `Enter ${addProjectRemoteSourceLabel(flow.source)} repository (${addProjectRemoteSourcePathHint(flow.source)})`; -} - -function buildPaletteRemoteSourceReadiness( - discovery: SourceControlDiscoveryResult | null, -): AddProjectRemoteSourceReadiness { - const readiness = buildAddProjectRemoteSourceReadiness(discovery); - return (source) => { - const entry = readiness(source); - return entry.hint === null - ? entry - : { - ...entry, - hint: entry.hint.replace( - "Open Source Control settings", - "Open Settings -> Source Control", - ), - }; - }; -} - -function errorMessage(error: unknown): string { - if (error instanceof Error && error.message.trim().length > 0) { - return error.message; - } - return "An error occurred."; -} - -const OVERLAY_MODE_BY_COMMAND = { - "commandPalette.toggle": "command", - "filePicker.toggle": "files", - "projectSearch.toggle": "content", -} as const satisfies Partial>; - -function overlayModeForCommand(command: string | null): SearchOverlayMode | null { - if (command === null) return null; - return command in OVERLAY_MODE_BY_COMMAND - ? OVERLAY_MODE_BY_COMMAND[command as keyof typeof OVERLAY_MODE_BY_COMMAND] - : null; -} - export function CommandPalette({ children }: { children: ReactNode }) { const navigate = useNavigate(); const [state, dispatch] = useReducer(reduceCommandPaletteUiState, { @@ -940,39 +854,10 @@ function OpenCommandPaletteDialog(props: { await handleNewThread(scopeProjectRef(project.environmentId, project.id)); }; - const projectSearchItems = buildProjectActionItems({ + const projectSearchItems = buildProjectSearchItems({ projects: pickerProjects, - valuePrefix: "project", - searchTerms: (project) => { - const members = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`) - ?.memberProjects ?? [project]; - return buildCommandPaletteProjectMetadata({ - projects: members, - locationByEnvironmentId: projectEnvironmentLocationById, - }).searchTerms; - }, - renderDescription: (project) => { - const members = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`) - ?.memberProjects ?? [project]; - const metadata = buildCommandPaletteProjectMetadata({ - projects: members, - locationByEnvironmentId: projectEnvironmentLocationById, - }); - const location = projectEnvironmentLocationById.get(project.environmentId) ?? { - kind: "remote" as const, - label: "Remote", - machine: "server" as const, - }; - return ( - 1} - location={location} - workspaceRoot={project.workspaceRoot} - /> - ); - }, - icon: projectFaviconIcon, + projectGroupByTargetKey, + locationByEnvironmentId: projectEnvironmentLocationById, runProject: openProjectFromSearch, }); @@ -980,42 +865,10 @@ function OpenCommandPaletteDialog(props: { const isScratch = (project: CommandPaletteProject) => isScratchProject(project, scratchWorkspaceRootFor(project.environmentId)); const projectItems = enumerateCommandPaletteItems( - buildProjectActionItems({ + buildNewThreadInProjectItems({ projects: pickerProjects.filter((project) => !isScratch(project)), - valuePrefix: "new-thread-in", - searchTerms: (project) => { - const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); - const location = projectEnvironmentLocationById.get(project.environmentId); - return [ - ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? - []), - ...(location ? [location.label] : []), - ]; - }, - renderDescription: (project) => { - const location = projectEnvironmentLocationById.get(project.environmentId) ?? { - kind: "remote", - label: "Remote", - machine: "server" as const, - }; - return ( - - - {location.kind === "remote" ? ( - - ) : null} - {location.label} - - - {project.workspaceRoot} - - ); - }, - icon: projectFaviconIcon, + projectGroupByTargetKey, + locationByEnvironmentId: projectEnvironmentLocationById, runProject: async (project) => { const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); const contextualRefBelongsToGroup = @@ -3021,35 +2874,3 @@ function OpenCommandPaletteDialog(props: { ); } - -function ProjectSearchDescription(props: { - readonly environmentLabels: ReadonlyArray; - readonly grouped: boolean; - readonly location: { - readonly kind: "local" | "remote"; - readonly label: string; - readonly machine: EnvironmentMachineKind; - }; - readonly workspaceRoot: string; -}) { - if (!props.grouped) { - return ( - - - {props.location.kind === "remote" ? ( - - ) : null} - {props.location.label} - - - {props.workspaceRoot} - - ); - } - - return {props.environmentLabels.join(" · ")}; -} diff --git a/apps/web/src/components/CommandPaletteProjectItems.tsx b/apps/web/src/components/CommandPaletteProjectItems.tsx new file mode 100644 index 0000000000000..90373413be4fa --- /dev/null +++ b/apps/web/src/components/CommandPaletteProjectItems.tsx @@ -0,0 +1,117 @@ +import type { EnvironmentId, EnvironmentMachineKind } from "@t3tools/contracts"; + +import type { SidebarProjectSnapshot } from "../sidebarProjectGrouping"; +import type { Project } from "../types"; +import { + buildCommandPaletteProjectMetadata, + buildProjectActionItems, + type CommandPaletteProject, + ITEM_ICON_CLASS, +} from "./CommandPalette.logic"; +import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; +import { ProjectFavicon } from "./ProjectFavicon"; +import { COMMAND_PALETTE_META_ICON_CLASS, CommandPaletteMetaDot } from "./ThreadCommandSubtitle"; + +export interface CommandPaletteProjectLocation { + readonly kind: "local" | "remote"; + readonly label: string; + readonly machine: EnvironmentMachineKind; +} + +const UNKNOWN_PROJECT_LOCATION: CommandPaletteProjectLocation = { + kind: "remote", + label: "Remote", + machine: "server", +}; + +interface ProjectItemsInput { + readonly projects: ReadonlyArray; + readonly projectGroupByTargetKey: ReadonlyMap; + readonly locationByEnvironmentId: ReadonlyMap; + readonly runProject: (project: CommandPaletteProject) => Promise; +} + +function projectFaviconIcon(project: Project) { + return ; +} + +/** Projects as root search results; a grouped project matches on every member. */ +export function buildProjectSearchItems(input: ProjectItemsInput) { + const membersOf = (project: CommandPaletteProject) => + input.projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`)?.memberProjects ?? [ + project, + ]; + return buildProjectActionItems({ + projects: input.projects, + valuePrefix: "project", + searchTerms: (project) => + buildCommandPaletteProjectMetadata({ + projects: membersOf(project), + locationByEnvironmentId: input.locationByEnvironmentId, + }).searchTerms, + renderDescription: (project) => { + const members = membersOf(project); + const metadata = buildCommandPaletteProjectMetadata({ + projects: members, + locationByEnvironmentId: input.locationByEnvironmentId, + }); + return members.length > 1 ? ( + {metadata.environmentLabels.join(" · ")} + ) : ( + + ); + }, + icon: projectFaviconIcon, + runProject: input.runProject, + }); +} + +/** Projects as targets of the "New thread in..." submenu. */ +export function buildNewThreadInProjectItems(input: ProjectItemsInput) { + return buildProjectActionItems({ + projects: input.projects, + valuePrefix: "new-thread-in", + searchTerms: (project) => { + const group = input.projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); + const location = input.locationByEnvironmentId.get(project.environmentId); + return [ + ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? []), + ...(location ? [location.label] : []), + ]; + }, + renderDescription: (project) => ( + + ), + icon: projectFaviconIcon, + runProject: input.runProject, + }); +} + +function ProjectLocationDescription(props: { + readonly location: CommandPaletteProjectLocation | undefined; + readonly workspaceRoot: string; +}) { + const location = props.location ?? UNKNOWN_PROJECT_LOCATION; + return ( + + + {location.kind === "remote" ? ( + + ) : null} + {location.label} + + + {props.workspaceRoot} + + ); +} From 28a8d2b3d81699d6203671d62eae76cc8859681d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:13:20 -0700 Subject: [PATCH 017/179] refactor(web): the command palette's add-project views are components (#22707) Co-authored-by: Claude Opus 5.5 --- apps/web/src/components/CommandPalette.tsx | 280 +++++------------- .../components/CommandPaletteAddProject.tsx | 171 +++++++++++ 2 files changed, 244 insertions(+), 207 deletions(-) create mode 100644 apps/web/src/components/CommandPaletteAddProject.tsx diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 99037bffdcba2..5ee7676de3324 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -12,7 +12,6 @@ import { getNewProjectPublishTarget, getNewProjectPublishTargets, getNewProjectRepository, - type NewProjectPublishTarget, normalizePastedCloneUrl, addProjectRemoteSourceLabel, addProjectRemoteSourcePathHint, @@ -196,6 +195,14 @@ import { buildNewThreadInProjectItems, buildProjectSearchItems, } from "./CommandPaletteProjectItems"; +import { + CommandPaletteSubmitButton, + NewProjectPublishHostPicker, + NewProjectSummary, + RemoteRepositorySummary, + remoteProjectSourceIcon, + sourceControlHostIcon, +} from "./CommandPaletteAddProject"; import { useCommandPaletteThreadItems } from "./CommandPaletteThreadItems"; import { APPEARANCE_OPTIONS, @@ -203,11 +210,9 @@ import { useCommandPaletteThemeItems, } from "./CommandPaletteThemeItems"; import { - CommandPaletteTitleIcon, CommandPaletteVirtualizedResults, scrollCommandPaletteRowIntoView, } from "./CommandPaletteResults"; -import { sourceControlIcon } from "~/sourceControlPresentation"; import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; import { Checkbox } from "./ui/checkbox"; import { ProjectFilePicker } from "./files/ProjectFilePicker"; @@ -219,8 +224,6 @@ import { primaryServerKeybindingsAtom } from "../state/server"; import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings"; import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command"; import { Button } from "./ui/button"; -import { Kbd, KbdGroup } from "./ui/kbd"; -import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; import { stackedThreadToast, toastManager } from "./ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; @@ -232,20 +235,6 @@ import { useThreadRouteTarget } from "../hooks/useThreadRouteTarget"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; -function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { - if (source === "url") return ; - const Icon = sourceControlIcon(sourceControlClients.get(source)); - return ; -} - -function sourceControlHostIcon( - definition: NewProjectPublishTarget["definition"], - className: string, -): ReactNode { - const Icon = sourceControlIcon(definition); - return ; -} - export function CommandPalette({ children }: { children: ReactNode }) { const navigate = useNavigate(); const [state, dispatch] = useReducer(reduceCommandPaletteUiState, { @@ -599,7 +588,6 @@ function OpenCommandPaletteDialog(props: { const [newProjectPublishes, setNewProjectPublishes] = useState(false); const [newProjectPublishKind, setNewProjectPublishKind] = useState(null); - const [newProjectHostPickerOpen, setNewProjectHostPickerOpen] = useState(false); const [isCreatingNewProject, setIsCreatingNewProject] = useState(false); // State lags a render behind, so a repeated Enter could start a second create. const newProjectSubmittingRef = useRef(false); @@ -2091,18 +2079,6 @@ function OpenCommandPaletteDialog(props: { group.value === "directories" ? { ...group, label: "Select where to clone" } : group, ); - const remoteProjectContext = (() => { - if (addProjectCloneFlow?.step !== "confirm") { - return null; - } - - return { - title: addProjectCloneFlow.repository?.nameWithOwner ?? addProjectCloneFlow.repositoryInput, - description: addProjectCloneFlow.repository?.url ?? addProjectCloneFlow.remoteUrl, - icon: remoteProjectSourceIcon(addProjectCloneFlow.source, ITEM_ICON_CLASS), - }; - })(); - const newProjectsRoot = newProjectFlow ? newProjectsRootFor(newProjectFlow.environmentId) : null; const newProjectPathPreview = newProjectsRoot === null ? null : getNewProjectPathPreview(newProjectsRoot, newProjectName); @@ -2209,55 +2185,12 @@ function OpenCommandPaletteDialog(props: { titleTrailingContent: ( {newProjectPublishTargets.length > 1 ? ( - // The row toggles on click, so the picker keeps its clicks to itself. The - // row also cancels pointerdown to keep focus in the input, which drops the - // mousedown Select opens on, so the picker opens on click instead. - { - event.stopPropagation(); - // React bubbles the popup's clicks here through its portal; only the - // trigger opens the picker. - if (!event.currentTarget.contains(event.target as Node)) return; - if (!isCreatingNewProject) setNewProjectHostPickerOpen(true); - }} - > - - + ) : null} @@ -2622,106 +2555,56 @@ function OpenCommandPaletteDialog(props: { const inputAccessory = newProjectFlow !== null ? ( - - { - event.preventDefault(); - }} - onClick={() => { - void submitNewProject(); - }} - /> - } - > - {isCreatingNewProject ? "Creating" : "Create"} - - {createShortcutLabel} - - - {`Create (${createShortcutLabel})`} - + { + void submitNewProject(); + }} + /> ) : addProjectCloneFlow?.step === "repository" ? ( - - { - event.preventDefault(); - }} - onClick={() => { - void submitAddProjectCloneFlow(); - }} - /> - } - > - {isRemoteProjectPending ? "Working" : remoteProjectButtonLabel} - - Enter - - - {remoteProjectButtonLabel ?? "Continue"} (Enter) - + { + void submitAddProjectCloneFlow(); + }} + /> ) : isBrowsing ? ( - - { - event.preventDefault(); - }} - onClick={() => { - if (relativePathNeedsActiveProject) { - return; - } - if (isCloneDestinationStep) { - void submitAddProjectCloneFlow(resolvedAddProjectPath); - } else { - void handleAddProject(resolvedAddProjectPath); - } - }} - /> - } - > - - {isCloneDestinationStep && isRemoteProjectPending ? "Cloning" : submitActionLabel} - - - {hasHighlightedBrowseItem ? `${submitModifierLabel} Enter` : "Enter"} - - - - {isCloneDestinationStep && !canCloneProject + - + : "This connection cannot add projects." + } + disabled={ + !canCreateProject || + !canCreateProjectInEnvironment(browseEnvironment?.connection.phase) || + relativePathNeedsActiveProject || + (isCloneDestinationStep && (!canCloneProject || isRemoteProjectPending)) + } + onSubmit={() => { + if (relativePathNeedsActiveProject) { + return; + } + if (isCloneDestinationStep) { + void submitAddProjectCloneFlow(resolvedAddProjectPath); + } else { + void handleAddProject(resolvedAddProjectPath); + } + }} + /> ) : null; const footerActionLabel = @@ -2803,38 +2686,21 @@ function OpenCommandPaletteDialog(props: { value={query} > {newProjectPathPreview !== null ? ( -
-
- - - - - - {newProjectName.length > 0 ? newProjectName : "New project"} - - - {newProjectName.length > 0 - ? `Creates ${newProjectPathPreview}` - : `Goes in ${newProjectsRoot}`} - {newProjectEnvironmentLabel === null ? null : ` on ${newProjectEnvironmentLabel}`} - - -
-
+ ) : null} - {remoteProjectContext ? ( -
-
Repository
-
- {remoteProjectContext.icon} - - {remoteProjectContext.title} - - {remoteProjectContext.description} - - -
-
+ {addProjectCloneFlow?.step === "confirm" ? ( + ) : null} {browseAccessError ? (
diff --git a/apps/web/src/components/CommandPaletteAddProject.tsx b/apps/web/src/components/CommandPaletteAddProject.tsx new file mode 100644 index 0000000000000..808536403a4bb --- /dev/null +++ b/apps/web/src/components/CommandPaletteAddProject.tsx @@ -0,0 +1,171 @@ +import { + type AddProjectRemoteSource, + type NewProjectPublishTarget, +} from "@t3tools/client-runtime/operations/projects"; +import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients"; +import type { SourceControlProviderKind } from "@t3tools/contracts"; +import { FolderGit2Icon, LinkIcon } from "lucide-react"; +import { type ReactNode, useState } from "react"; + +import { sourceControlIcon } from "~/sourceControlPresentation"; +import { ITEM_ICON_CLASS } from "./CommandPalette.logic"; +import { CommandPaletteTitleIcon } from "./CommandPaletteResults"; +import { Button } from "./ui/button"; +import { Kbd, KbdGroup } from "./ui/kbd"; +import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; + +export function remoteProjectSourceIcon( + source: AddProjectRemoteSource, + className: string, +): ReactNode { + if (source === "url") return ; + const Icon = sourceControlIcon(sourceControlClients.get(source)); + return ; +} + +export function sourceControlHostIcon( + definition: NewProjectPublishTarget["definition"], + className: string, +): ReactNode { + const Icon = sourceControlIcon(definition); + return ; +} + +/** The input's trailing submit button for the add-project, clone, and new-project flows. */ +export function CommandPaletteSubmitButton(props: { + readonly label: ReactNode; + readonly shortcutLabel: string; + readonly ariaLabel: string; + readonly tooltip: ReactNode; + readonly disabled: boolean; + readonly onSubmit: () => void; +}) { + return ( + + { + event.preventDefault(); + }} + onClick={props.onSubmit} + /> + } + > + {props.label} + + {props.shortcutLabel} + + + {props.tooltip} + + ); +} + +/** + * Picks which host a new project publishes to. The row it sits in toggles on click, so the + * picker keeps its clicks to itself. The row also cancels pointerdown to keep focus in the + * input, which drops the mousedown Select opens on, so the picker opens on click instead. + */ +export function NewProjectPublishHostPicker(props: { + readonly disabled: boolean; + readonly targets: ReadonlyArray; + readonly value: SourceControlProviderKind; + readonly onValueChange: (kind: SourceControlProviderKind) => void; +}) { + const [open, setOpen] = useState(false); + return ( + { + event.stopPropagation(); + // React bubbles the popup's clicks here through its portal; only the + // trigger opens the picker. + if (!event.currentTarget.contains(event.target as Node)) return; + if (!props.disabled) setOpen(true); + }} + > + + + ); +} + +export function NewProjectSummary(props: { + readonly name: string; + readonly pathPreview: string; + readonly projectsRoot: string | null; + readonly environmentLabel: string | null; +}) { + return ( +
+
+ + + + + + {props.name.length > 0 ? props.name : "New project"} + + + {props.name.length > 0 + ? `Creates ${props.pathPreview}` + : `Goes in ${props.projectsRoot}`} + {props.environmentLabel === null ? null : ` on ${props.environmentLabel}`} + + +
+
+ ); +} + +export function RemoteRepositorySummary(props: { + readonly source: AddProjectRemoteSource; + readonly title: string; + readonly description: string; +}) { + return ( +
+
Repository
+
+ + {remoteProjectSourceIcon(props.source, ITEM_ICON_CLASS)} + + + {props.title} + {props.description} + +
+
+ ); +} From 955b4cc80166fd0bfd01fe2ec40285bd755f986f Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Wed, 21 Oct 2026 01:17:04 -0700 Subject: [PATCH 018/179] feat(cli): start, message, and read threads from the terminal with t3 thread (#22732) Co-authored-by: Claude Opus 5.5 --- apps/server/src/auth/EnvironmentAuth.ts | 12 +- apps/server/src/binCli.ts | 2 + apps/server/src/cli/thread.test.ts | 75 ++ apps/server/src/cli/thread.ts | 710 ++++++++++++++++++ apps/server/src/mcp/AcpMcpStdioBridge.ts | 166 ++-- .../src/mcp/toolkits/project/handlers.test.ts | 125 ++- .../src/mcp/toolkits/project/handlers.ts | 29 +- docs/user/outside-agents.md | 22 + 8 files changed, 1041 insertions(+), 100 deletions(-) create mode 100644 apps/server/src/cli/thread.test.ts create mode 100644 apps/server/src/cli/thread.ts diff --git a/apps/server/src/auth/EnvironmentAuth.ts b/apps/server/src/auth/EnvironmentAuth.ts index 569ae75907d11..1a6d3206833ab 100644 --- a/apps/server/src/auth/EnvironmentAuth.ts +++ b/apps/server/src/auth/EnvironmentAuth.ts @@ -558,7 +558,11 @@ export class EnvironmentAuth extends Context.Service< readonly access: AuthMcpClientAccess; readonly client: AuthClientMetadata; }) => Effect.Effect< - { readonly token: string; readonly expiresAt: DateTime.DateTime }, + { + readonly sessionId: AuthSessionId; + readonly token: string; + readonly expiresAt: DateTime.DateTime; + }, ServerAuthInternalError >; /** @@ -1292,7 +1296,11 @@ export const make = Effect.gen(function* () { client: { ...input.client, label: input.label, deviceType: "bot" }, }) .pipe( - Effect.map((issued) => ({ token: issued.token, expiresAt: issued.expiresAt })), + Effect.map((issued) => ({ + sessionId: issued.sessionId, + token: issued.token, + expiresAt: issued.expiresAt, + })), Effect.mapError((cause) => new ServerAuthSessionTokenIssueError({ cause })), Effect.withSpan("EnvironmentAuth.issueMcpClientSession"), ); diff --git a/apps/server/src/binCli.ts b/apps/server/src/binCli.ts index b0a2ceffef8f6..2f7c24536caaa 100644 --- a/apps/server/src/binCli.ts +++ b/apps/server/src/binCli.ts @@ -18,6 +18,7 @@ import { hasCloudPublicConfig } from "./cloud/publicConfig.ts"; import { extendConnectionAttemptTimeout, keepOutboundFetchOnHttp1 } from "./outboundHttp.ts"; import { sharedServerCommandFlags } from "./cli/config.ts"; import { projectCommand } from "./cli/project.ts"; +import { threadCommand } from "./cli/thread.ts"; import { runDefaultServerCommand, serveCommand, startCommand } from "./cli/server.ts"; import { updateCommand } from "./cli/update.ts"; import { uninstallCommand } from "./cli/uninstall.ts"; @@ -76,6 +77,7 @@ export const makeCli = ({ cloudEnabled = hasCloudPublicConfig } = {}) => authCommand, browserCommand, projectCommand, + threadCommand, serviceCommand, updateCommand, uninstallCommand, diff --git a/apps/server/src/cli/thread.test.ts b/apps/server/src/cli/thread.test.ts new file mode 100644 index 0000000000000..c2e4bc5c8168c --- /dev/null +++ b/apps/server/src/cli/thread.test.ts @@ -0,0 +1,75 @@ +import { assert, it } from "@effect/vitest"; +import { RunId, ThreadId } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; + +import { type ThreadTools, waitForRun } from "./thread.ts"; + +const threadId = ThreadId.make("thread-1"); +const runId = RunId.make("run-1"); + +/** Answers each tool call with the next scripted response for that tool. */ +const scriptedTools = (script: Record>) => { + const calls: Array<{ readonly tool: string; readonly args: unknown }> = []; + const call = ((tool: string, args: unknown) => { + calls.push({ tool, args }); + const next = script[tool]?.shift(); + return next === undefined ? Effect.die(`unscripted ${tool} call`) : Effect.succeed(next); + }) as ThreadTools["call"]; + return { tools: { call } satisfies ThreadTools, calls }; +}; + +it.effect("--wait follows the sent run to its continuation's terminal status", () => + Effect.gen(function* () { + const continuation = RunId.make("run-2"); + const { tools, calls } = scriptedTools({ + t3_thread_wait: [ + { threadId, runId, status: "running", timedOut: true }, + { threadId, runId: continuation, status: "failed", timedOut: false }, + ], + t3_thread_read: [{ thread: { pendingRequestCount: 0 } }], + }); + + const outcome = yield* waitForRun(tools, threadId, runId); + + assert.deepStrictEqual(outcome, { type: "ended", runId: continuation, status: "failed" }); + assert.deepStrictEqual( + calls.filter((entry) => entry.tool === "t3_thread_wait").map((entry) => entry.args), + [ + { threadId, runId, timeoutMs: 15_000 }, + { threadId, runId, timeoutMs: 15_000 }, + ], + ); + }), +); + +it.effect("--wait stops when the thread waits for an answer instead of polling forever", () => + Effect.gen(function* () { + const { tools } = scriptedTools({ + t3_thread_wait: [ + { threadId, runId, status: "queued", timedOut: true }, + { threadId, runId, status: "queued", timedOut: true }, + ], + t3_thread_read: [{ thread: { pendingRequestCount: 1 } }], + }); + + assert.deepStrictEqual(yield* waitForRun(tools, threadId, runId), { type: "needs_input" }); + }), +); + +it.effect("--wait reports its run's result when a later run is the one waiting", () => + Effect.gen(function* () { + const { tools } = scriptedTools({ + t3_thread_wait: [ + { threadId, runId, status: "running", timedOut: true }, + { threadId, runId, status: "completed", timedOut: false }, + ], + t3_thread_read: [{ thread: { pendingRequestCount: 1 } }], + }); + + assert.deepStrictEqual(yield* waitForRun(tools, threadId, runId), { + type: "ended", + runId, + status: "completed", + }); + }), +); diff --git a/apps/server/src/cli/thread.ts b/apps/server/src/cli/thread.ts new file mode 100644 index 0000000000000..989028afccb9d --- /dev/null +++ b/apps/server/src/cli/thread.ts @@ -0,0 +1,710 @@ +import { + OrchestratorMcpFailure, + OrchestratorMcpThreadListResult, + OrchestratorMcpThreadReadResult, + OrchestratorMcpThreadSendResult, + OrchestratorMcpThreadWaitResult, + ProjectId, + RunId, + ThreadId, + TrimmedNonEmptyString, + type OrchestratorMcpThreadListItem, + type OrchestratorMcpThreadStatus, + type OrchestratorMcpThreadTimelineItem, +} from "@t3tools/contracts"; +import * as HostProcess from "@t3tools/shared/HostProcess"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as References from "effect/References"; +import * as Runtime from "effect/Runtime"; +import * as Schema from "effect/Schema"; +import * as Stdio from "effect/Stdio"; +import * as Stream from "effect/Stream"; +import { Argument, Command, Flag, GlobalFlag } from "effect/cli"; + +import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; +import * as ServerConfig from "../config.ts"; +import { mcpToolSession, type McpToolSession } from "../mcp/AcpMcpStdioBridge.ts"; +import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts"; +import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; + +/** + * Short enough that a run which stops to ask the user something is noticed + * soon, since `t3_thread_wait` only returns on a terminal run. + */ +const WAIT_SLICE_MS = 15_000; +const READ_PAGE_LIMIT = 100; +const TITLE_MAX_CHARS = 80; + +export class ThreadServerNotRunningError extends Schema.TaggedError()( + "ThreadServerNotRunningError", + { baseDir: Schema.String }, +) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + return `No running T3 Code server uses ${this.baseDir}. Start T3 Code, then retry.`; + } +} + +export class ThreadServerUnreachableError extends Schema.TaggedError()( + "ThreadServerUnreachableError", + { origin: Schema.String, cause: Schema.Defect() }, +) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + const reason = this.cause instanceof Error ? ` ${this.cause.message}` : ""; + return `Could not reach the T3 Code server at ${this.origin}.${reason}`; + } +} + +export class ThreadToolFailedError extends Schema.TaggedError()( + "ThreadToolFailedError", + { tool: Schema.String, detail: Schema.String }, +) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + return this.detail; + } +} + +export class ThreadUsageError extends Schema.TaggedError()("ThreadUsageError", { + detail: Schema.String, +}) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + return this.detail; + } +} + +export class ThreadProjectNotFoundError extends Schema.TaggedError()( + "ThreadProjectNotFoundError", + { identifier: Schema.String }, +) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + return `No T3 Code project matches '${this.identifier}'. Pass a project id or a path inside a project.`; + } +} + +/** Exit code 2 lets a script tell "needs a human" apart from a failed run. */ +export class ThreadNeedsInputError extends Schema.TaggedError()( + "ThreadNeedsInputError", + { threadId: ThreadId }, +) { + override readonly [Runtime.errorReported] = false; + override readonly [Runtime.errorExitCode] = 2; + + override get message(): string { + return `Thread ${this.threadId} is waiting for an answer. Open it in T3 Code to respond.`; + } +} + +export class ThreadRunEndedError extends Schema.TaggedError()( + "ThreadRunEndedError", + { threadId: ThreadId, status: Schema.String, error: Schema.optional(Schema.String) }, +) { + override readonly [Runtime.errorReported] = false; + override get message(): string { + const reason = this.error === undefined ? "." : `: ${this.error}`; + return `Thread ${this.threadId} stopped with status ${this.status}${reason}`; + } +} + +const ThreadCliError = Schema.Union([ + ThreadServerNotRunningError, + ThreadServerUnreachableError, + ThreadToolFailedError, + ThreadUsageError, + ThreadProjectNotFoundError, + ThreadNeedsInputError, + ThreadRunEndedError, +]); +const isThreadCliError = Schema.is(ThreadCliError); + +/** + * These errors are expected outcomes for a script, so they print as one line + * on stderr instead of the runtime's logged stack trace. + */ +const reportThreadErrors = (effect: Effect.Effect) => + effect.pipe( + Effect.tapError((error) => + isThreadCliError(error) ? Console.error(error.message) : Effect.void, + ), + ); + +const decodeFailureText = Schema.decodeUnknownOption(Schema.fromJsonString(OrchestratorMcpFailure)); +const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); +const printJson = (value: unknown) => Console.log(encodeJson(value)); + +const ToolResult = Schema.Struct({ + isError: Schema.optional(Schema.Boolean), + structuredContent: Schema.optional(Schema.Unknown), + content: Schema.optional( + Schema.Array(Schema.Struct({ type: Schema.String, text: Schema.optional(Schema.String) })), + ), +}); + +const decodeToolResult = Schema.decodeUnknownEffect(ToolResult); + +const ProjectListResult = Schema.Struct({ + projects: Schema.Array( + Schema.Struct({ id: ProjectId, title: Schema.String, workspaceRoot: Schema.String }), + ), + nextCursor: Schema.NullOr(Schema.Int), +}); + +const ThreadLaunchResult = Schema.Struct({ + threadId: ThreadId, + projectId: ProjectId, + runId: Schema.NullOr(RunId), +}); + +const toolFailureDetail = (result: typeof ToolResult.Type) => { + const text = result.content?.find((part) => part.text !== undefined)?.text ?? ""; + return Option.match(decodeFailureText(text), { + onNone: () => text || "The tool call failed.", + onSome: (failure) => failure.message, + }); +}; + +const makeThreadTools = (session: McpToolSession, origin: string) => { + const unexpected = (tool: string) => + new ThreadToolFailedError({ + tool, + detail: `The server sent an unexpected ${tool} result. Update the t3 CLI to match the server.`, + }); + return { + call: Effect.fn("cli.thread.callTool")(function* >( + tool: string, + args: Readonly>, + schema: S, + ): Effect.fn.Return { + const raw = yield* session + .call(tool, args) + .pipe(Effect.mapError((cause) => new ThreadServerUnreachableError({ origin, cause }))); + const result = yield* decodeToolResult(raw).pipe(Effect.mapError(() => unexpected(tool))); + if (result.isError === true) { + return yield* new ThreadToolFailedError({ tool, detail: toolFailureDetail(result) }); + } + return yield* Schema.decodeUnknownEffect(schema)(result.structuredContent).pipe( + Effect.mapError(() => unexpected(tool)), + ); + }), + }; +}; +export type ThreadTools = ReturnType; + +/** + * Signs in as a short-lived MCP client of the running server, the same way an + * outside agent does, and revokes that session when the command ends. + */ +const withThreadTools = Effect.fn("cli.thread.withThreadTools")(function* ( + flags: { readonly baseDir: Option.Option }, + run: (tools: ThreadTools) => Effect.Effect, +) { + const logLevel = yield* GlobalFlag.LogLevel; + const config = yield* resolveCliAuthConfig(flags, logLevel); + return yield* Effect.gen(function* () { + const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); + if (Option.isNone(runtimeState)) { + return yield* new ThreadServerNotRunningError({ baseDir: config.baseDir }); + } + const origin = runtimeState.value.origin; + const environmentAuth = yield* EnvironmentAuth.EnvironmentAuth; + const issued = yield* Effect.acquireRelease( + environmentAuth.issueMcpClientSession({ + label: "t3 thread cli", + // The CLI runs as the machine's owner. New threads still start at + // the project's default mode, never at this ceiling by default. + access: "full-access", + client: { deviceType: "bot" }, + }), + (session) => + environmentAuth.revokeSession(session.sessionId).pipe(Effect.ignore({ log: true })), + ); + const session = yield* mcpToolSession({ + endpoint: new URL("/mcp", origin).href, + authorization: `Bearer ${issued.token}`, + clientName: "t3-thread-cli", + }).pipe(Effect.mapError((cause) => new ThreadServerUnreachableError({ origin, cause }))); + return yield* run(makeThreadTools(session, origin)); + }).pipe( + Effect.scoped, + Effect.provide( + EnvironmentAuth.layerRuntime.pipe( + Layer.provide(ServerConfig.layer(config)), + Layer.provide(Layer.succeed(References.MinimumLogLevel, config.logLevel)), + ), + ), + ); +}); + +const readMessageArgument = Effect.fn("cli.thread.readMessage")(function* (argument: string) { + const text = + argument === "-" + ? yield* Stdio.Stdio.pipe( + Effect.flatMap((stdio) => stdio.stdin.pipe(Stream.decodeText(), Stream.mkString)), + ) + : argument; + if (text.trim().length === 0) { + return yield* new ThreadUsageError({ detail: "The message is empty." }); + } + return text; +}); + +const listAllProjects = Effect.fn("cli.thread.listAllProjects")(function* (tools: ThreadTools) { + const projects: Array<(typeof ProjectListResult.Type)["projects"][number]> = []; + let cursor: number | null = 0; + while (cursor !== null) { + const page: typeof ProjectListResult.Type = yield* tools.call( + "t3_project_list", + { cursor, limit: 100 }, + ProjectListResult, + ); + projects.push(...page.projects); + cursor = page.nextCursor; + } + return projects; +}); + +/** A project id, or a path inside a project's folder (deepest project wins). */ +const resolveProject = Effect.fn("cli.thread.resolveProject")(function* ( + tools: ThreadTools, + identifier: string, +) { + const projects = yield* listAllProjects(tools); + const byId = projects.find((project) => project.id === identifier); + if (byId !== undefined) return byId; + + const path = yield* Path.Path; + const fileSystem = yield* FileSystem.FileSystem; + // Stored roots keep the path the project was added with, which may pass + // through a symlink (macOS /tmp), so both sides compare as real paths. + const realPath = (value: string) => + fileSystem.realPath(value).pipe(Effect.orElseSucceed(() => value)); + const target = yield* realPath(path.resolve(yield* HostProcess.WorkingDirectory, identifier)); + const roots = yield* Effect.forEach(projects, (project) => + Effect.map(realPath(project.workspaceRoot), (root) => ({ project, root })), + ); + const containing = roots + .filter(({ root }) => { + const relative = path.relative(root, target); + return ( + relative === "" || + (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)) + ); + }) + .toSorted((left, right) => right.root.length - left.root.length)[0]; + if (containing === undefined) { + return yield* new ThreadProjectNotFoundError({ identifier }); + } + return containing.project; +}); + +/** Every item on a timeline view, oldest first. */ +const readTimeline = Effect.fn("cli.thread.readTimeline")(function* ( + tools: ThreadTools, + threadId: ThreadId, + view: "messages" | "activity", +) { + const items: Array = []; + let afterPosition: number | undefined; + let page: OrchestratorMcpThreadReadResult; + do { + page = yield* tools.call( + "t3_thread_read", + { + threadId, + view, + limit: READ_PAGE_LIMIT, + runLimit: 1, + ...(afterPosition === undefined ? {} : { afterPosition }), + }, + OrchestratorMcpThreadReadResult, + ); + items.push(...page.items); + afterPosition = page.nextPosition ?? undefined; + } while (page.hasMore && afterPosition !== undefined); + return { thread: page.thread, items }; +}); + +/** Item text past the read's per-item cap. */ +const readFullText = Effect.fn("cli.thread.readFullText")(function* ( + tools: ThreadTools, + threadId: ThreadId, + item: OrchestratorMcpThreadTimelineItem, +) { + let text = item.text ?? ""; + let nextTextOffset = item.textTruncated ? (item.nextTextOffset ?? null) : null; + while (nextTextOffset !== null) { + const page = yield* tools.call( + "t3_thread_read", + { threadId, itemId: item.itemId, textOffset: nextTextOffset, runLimit: 1 }, + OrchestratorMcpThreadReadResult, + ); + const chunk = page.items.find((candidate) => candidate.itemId === item.itemId); + if (chunk === undefined) break; + text += chunk.text ?? ""; + nextTextOffset = chunk.textTruncated ? (chunk.nextTextOffset ?? null) : null; + } + return text; +}); + +/** The run's last assistant message, or its last error when it did not complete. */ +const runResultText = Effect.fn("cli.thread.runResultText")(function* ( + tools: ThreadTools, + threadId: ThreadId, + runId: RunId, + type: "assistant_message" | "error", +) { + const { items } = yield* readTimeline( + tools, + threadId, + type === "assistant_message" ? "messages" : "activity", + ); + const item = items.findLast((candidate) => candidate.runId === runId && candidate.type === type); + return item === undefined ? null : yield* readFullText(tools, threadId, item); +}); + +type WaitOutcome = + | { + readonly type: "ended"; + readonly runId: RunId | null; + readonly status: OrchestratorMcpThreadStatus; + } + | { readonly type: "needs_input" }; + +/** + * Waits for this exact run, following a restart to its continuation, so a + * message another client sends meanwhile cannot stand in for its result. + */ +export const waitForRun = Effect.fn("cli.thread.waitForRun")(function* ( + tools: ThreadTools, + threadId: ThreadId, + runId: RunId, +) { + const waitSlice = (timeoutMs: number) => + tools.call("t3_thread_wait", { threadId, runId, timeoutMs }, OrchestratorMcpThreadWaitResult); + const ended = (waited: OrchestratorMcpThreadWaitResult) => + ({ type: "ended", runId: waited.runId, status: waited.status }) satisfies WaitOutcome; + while (true) { + const waited = yield* waitSlice(WAIT_SLICE_MS); + if (!waited.timedOut) return ended(waited); + // A pending request on this run, or on an earlier run this one is queued + // behind, blocks it until someone answers. + const { thread } = yield* tools.call( + "t3_thread_read", + { threadId, limit: 1, runLimit: 1 }, + OrchestratorMcpThreadReadResult, + ); + if (thread.pendingRequestCount > 0) { + // The count is thread-wide: if this run ended since the slice, the + // request belongs to a later run. + const recheck = yield* waitSlice(1); + return recheck.timedOut ? ({ type: "needs_input" } satisfies WaitOutcome) : ended(recheck); + } + } +}); + +/** + * With --json the result always reaches stdout, so a script keeps the thread + * id even when the run fails. + */ +const finishRun = Effect.fn("cli.thread.finishRun")(function* ( + tools: ThreadTools, + input: { + readonly threadId: ThreadId; + readonly runId: RunId; + readonly json: boolean; + }, +) { + const outcome = yield* waitForRun(tools, input.threadId, input.runId); + const runId = outcome.type === "ended" ? (outcome.runId ?? input.runId) : input.runId; + const status = outcome.type === "ended" ? outcome.status : "waiting"; + const reply = + status === "completed" + ? yield* runResultText(tools, input.threadId, runId, "assistant_message") + : null; + const error = + status === "completed" || status === "waiting" + ? null + : yield* runResultText(tools, input.threadId, runId, "error"); + if (input.json) { + yield* printJson({ threadId: input.threadId, runId, status, reply, error }); + } else if (reply !== null) { + yield* Console.log(reply); + } + if (outcome.type === "needs_input") { + return yield* new ThreadNeedsInputError({ threadId: input.threadId }); + } + if (status !== "completed") { + return yield* new ThreadRunEndedError({ + threadId: input.threadId, + status, + ...(error === null ? {} : { error }), + }); + } +}); + +const titleFromMessage = (message: string) => { + const firstLine = message.trim().split("\n")[0]?.trim() ?? ""; + return firstLine.length > TITLE_MAX_CHARS + ? `${firstLine.slice(0, TITLE_MAX_CHARS - 1).trimEnd()}…` + : firstLine; +}; + +const launchWorkspaceStrategy = (input: { + readonly worktree: Option.Option; + readonly branch: Option.Option; +}) => + Option.match(input.worktree, { + onNone: () => + Option.isSome(input.branch) + ? Effect.fail(new ThreadUsageError({ detail: "--branch needs --worktree ." })) + : Effect.succeed(undefined), + onSome: (baseRef) => + Effect.succeed({ + type: "worktree" as const, + baseRef, + ...Option.match(input.branch, { onNone: () => ({}), onSome: (branch) => ({ branch }) }), + }), + }); + +const jsonFlag = Flag.Boolean("json").pipe( + Flag.withDescription("Print JSON instead of text."), + Flag.withDefault(false), +); +const waitFlag = Flag.Boolean("wait").pipe( + Flag.withDescription( + "Wait for the agent to finish and print its reply. Exits 1 if the run fails and 2 if it waits for an answer.", + ), + Flag.withDefault(false), +); +const messageArgument = Argument.String("message").pipe( + Argument.withDescription("Message for the agent, or - to read it from stdin."), +); +const threadArgument = Argument.String("thread").pipe(Argument.withDescription("Thread id.")); + +const decodeThreadIdValue = Schema.decodeUnknownEffect(ThreadId); +const decodeTitle = Schema.decodeUnknownEffect(TrimmedNonEmptyString); +const decodeThreadId = (value: string) => + decodeThreadIdValue(value.trim()).pipe( + Effect.mapError(() => new ThreadUsageError({ detail: `'${value}' is not a thread id.` })), + ); + +const threadListCommand = Command.make("list", { + ...projectLocationFlags, + project: Flag.String("project").pipe( + Flag.withDescription("Project id or a path inside the project. Defaults to this directory."), + Flag.withDefault("."), + ), + json: jsonFlag, +}).pipe( + Command.withDescription("List a project's threads, newest first."), + Command.withHandler((flags) => + withThreadTools( + flags, + Effect.fn("cli.thread.list")(function* (tools) { + const project = yield* resolveProject(tools, flags.project); + const threads: Array = []; + let cursor: number | null = 0; + while (cursor !== null) { + const page: OrchestratorMcpThreadListResult = yield* tools.call( + "t3_thread_list", + { projectId: project.id, includeSubagents: false, limit: 100, cursor }, + OrchestratorMcpThreadListResult, + ); + threads.push(...page.threads); + cursor = page.nextCursor; + } + if (flags.json) { + return yield* printJson(threads); + } + for (const thread of threads) { + yield* Console.log(`${thread.threadId} ${thread.status.padEnd(11)} ${thread.title}`); + } + }), + ).pipe(reportThreadErrors), + ), +); + +const threadStartCommand = Command.make("start", { + ...projectLocationFlags, + project: Argument.String("project").pipe( + Argument.withDescription("Project id or a path inside the project, such as ."), + ), + message: messageArgument, + title: Flag.String("title").pipe( + Flag.withDescription("Thread title. Defaults to the message's first line."), + Flag.optional, + ), + provider: Flag.String("provider").pipe( + Flag.withDescription("Provider instance id. Pass with --model."), + Flag.optional, + ), + model: Flag.String("model").pipe( + Flag.withDescription("Model slug. Defaults to the project's default model."), + Flag.optional, + ), + worktree: Flag.String("worktree").pipe( + Flag.withDescription( + "Run in a new git worktree based on this branch, tag, or commit, instead of the project folder.", + ), + Flag.optional, + ), + branch: Flag.String("branch").pipe( + Flag.withDescription("Branch name for the new worktree."), + Flag.optional, + ), + wait: waitFlag, + json: jsonFlag, +}).pipe( + Command.withDescription("Start a thread in a project and print its id."), + Command.withHandler((flags) => + Effect.gen(function* () { + const message = yield* readMessageArgument(flags.message); + const workspaceStrategy = yield* launchWorkspaceStrategy(flags); + if (Option.isSome(flags.provider) !== Option.isSome(flags.model)) { + return yield* new ThreadUsageError({ detail: "--provider and --model go together." }); + } + const title = yield* decodeTitle( + Option.getOrElse(flags.title, () => titleFromMessage(message)), + ).pipe(Effect.mapError(() => new ThreadUsageError({ detail: "The title is empty." }))); + yield* withThreadTools( + flags, + Effect.fn("cli.thread.start")(function* (tools) { + const project = yield* resolveProject(tools, flags.project); + const launched = yield* tools.call( + "t3_thread_launch", + { + projectId: project.id, + title, + message, + ...(workspaceStrategy === undefined ? {} : { workspaceStrategy }), + ...(Option.isSome(flags.provider) && Option.isSome(flags.model) + ? { + modelSelection: { + instanceId: flags.provider.value, + model: flags.model.value, + }, + } + : {}), + }, + ThreadLaunchResult, + ); + if (flags.wait && launched.runId !== null) { + if (!flags.json) yield* Console.error(`Thread ${launched.threadId}`); + return yield* finishRun(tools, { + threadId: launched.threadId, + runId: launched.runId, + json: flags.json, + }); + } + yield* flags.json ? printJson(launched) : Console.log(launched.threadId); + }), + ); + }).pipe(reportThreadErrors), + ), +); + +const threadSendCommand = Command.make("send", { + ...projectLocationFlags, + thread: threadArgument, + message: messageArgument, + wait: waitFlag, + json: jsonFlag, +}).pipe( + Command.withDescription( + "Send a message to a thread. A busy thread gets it as its next turn; nothing is interrupted.", + ), + Command.withHandler((flags) => + Effect.gen(function* () { + const threadId = yield* decodeThreadId(flags.thread); + const message = yield* readMessageArgument(flags.message); + yield* withThreadTools( + flags, + Effect.fn("cli.thread.send")(function* (tools) { + const sent = yield* tools.call( + "t3_thread_send", + { threadId, message, mode: "queue" }, + OrchestratorMcpThreadSendResult, + ); + if (flags.wait) { + return yield* finishRun(tools, { threadId, runId: sent.runId, json: flags.json }); + } + yield* flags.json + ? printJson(sent) + : Console.log(sent.delivery === "queued" ? "Queued." : "Sent."); + }), + ); + }).pipe(reportThreadErrors), + ), +); + +const threadShowCommand = Command.make("show", { + ...projectLocationFlags, + thread: threadArgument, + messages: Flag.Int("messages").pipe( + Flag.withDescription("How many of the latest messages to print."), + Flag.withDefault(10), + ), + json: jsonFlag, +}).pipe( + Command.withDescription("Show a thread's state and its latest messages."), + Command.withHandler((flags) => + Effect.gen(function* () { + const threadId = yield* decodeThreadId(flags.thread); + yield* withThreadTools( + flags, + Effect.fn("cli.thread.show")(function* (tools) { + const { thread, items } = yield* readTimeline(tools, threadId, "messages"); + const conversation = items.filter( + (item) => item.type === "user_message" || item.type === "assistant_message", + ); + const latest = conversation.slice(Math.max(0, conversation.length - flags.messages)); + const messages = yield* Effect.forEach(latest, (item) => + readFullText(tools, threadId, item).pipe( + Effect.map((text) => ({ + role: item.type === "user_message" ? "user" : "assistant", + runId: item.runId, + text, + })), + ), + ); + if (flags.json) { + return yield* printJson({ thread, messages }); + } + yield* Console.log(thread.title); + yield* Console.log( + [thread.threadId, thread.status, `${thread.providerInstanceId}/${thread.model}`].join( + " ", + ), + ); + if (thread.worktreePath !== null) { + yield* Console.log(`${thread.branch ?? "(no branch)"} ${thread.worktreePath}`); + } + if (thread.pendingRequestCount > 0) { + yield* Console.log(`Waiting for ${thread.pendingRequestCount} answer(s).`); + } + for (const entry of messages) { + yield* Console.log(`\n[${entry.role}]\n${entry.text}`); + } + }), + ); + }).pipe(reportThreadErrors), + ), +); + +export const threadCommand = Command.make("thread").pipe( + Command.withDescription("Start, message, and read threads on the running server."), + Command.withSubcommands([ + threadListCommand, + threadStartCommand, + threadSendCommand, + threadShowCommand, + ]), +); diff --git a/apps/server/src/mcp/AcpMcpStdioBridge.ts b/apps/server/src/mcp/AcpMcpStdioBridge.ts index 7338190ca56ad..2db36ce5abc28 100644 --- a/apps/server/src/mcp/AcpMcpStdioBridge.ts +++ b/apps/server/src/mcp/AcpMcpStdioBridge.ts @@ -6,6 +6,7 @@ import * as NodeReadline from "node:readline"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; +import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import { discardResponseBody, responsePayloads } from "@t3tools/shared/mcpResponsePayloads"; @@ -14,7 +15,7 @@ import { discardResponseBody, responsePayloads } from "@t3tools/shared/mcpRespon * unevenly implemented. */ -class AcpMcpBridgeError extends Error { +export class AcpMcpBridgeError extends Error { readonly _tag = "AcpMcpBridgeError"; } @@ -53,94 +54,125 @@ function protocolVersionOf(entry: unknown): string | null { return typeof version === "string" && version.length > 0 ? version : null; } -export interface AcpMcpToolCallOptions { +export interface McpToolSessionOptions { readonly endpoint: string; readonly authorization: string; - readonly tool: string; - readonly arguments: Readonly>; + readonly clientName: string; readonly fetchImplementation?: (url: string, init?: RequestInit) => Promise; } +export interface McpToolSession { + /** Resolves to the JSON-RPC `result` of `tools/call`, a `CallToolResult`. */ + readonly call: ( + tool: string, + args: Readonly>, + ) => Effect.Effect; +} + /** - * This is the terminal fallback for ACP agents that accept `mcpServers` in - * `session/new` but fail to expose those tools to their model. Compliant ACP - * agents continue to use the stdio bridge above. + * One initialized MCP session over T3 Code's HTTP endpoint, terminated when + * the scope closes. Used by the ACP terminal fallback and `t3 thread`. */ -function callAcpMcpTool(options: AcpMcpToolCallOptions): Effect.Effect { +export const mcpToolSession = ( + options: McpToolSessionOptions, +): Effect.Effect => { const fetchImplementation = options.fetchImplementation ?? fetch; - return Effect.gen(function* () { - let sessionId: string | null = null; - let protocolVersion: string | null = null; + let sessionId: string | null = null; + let protocolVersion: string | null = null; + let nextId = 0; - const send = Effect.fn("AcpMcpStdioBridge.send")(function* ( - message: unknown, - ): Effect.fn.Return, AcpMcpBridgeError> { - const response = yield* Effect.tryPromise({ - try: () => - fetchImplementation(options.endpoint, { - method: "POST", - headers: { - "content-type": "application/json", - accept: "application/json, text/event-stream", - authorization: options.authorization, - ...(sessionId === null ? {} : { "mcp-session-id": sessionId }), - ...(protocolVersion === null ? {} : { "mcp-protocol-version": protocolVersion }), - }, - body: JSON.stringify(message), - }), - catch: bridgeError, - }); - sessionId = response.headers.get("mcp-session-id") ?? sessionId; - if (!response.ok) { - yield* discardResponseBody(response); - return yield* Effect.fail( - new AcpMcpBridgeError(`T3 Code MCP endpoint responded with HTTP ${response.status}.`), - ); - } - const payloads = yield* Stream.runCollect(responsePayloads(response, bridgeError)); - for (const payload of payloads) { - protocolVersion = protocolVersionOf(payload) ?? protocolVersion; - } - return payloads; - }); + const headers = () => ({ + accept: "application/json, text/event-stream", + authorization: options.authorization, + ...(sessionId === null ? {} : { "mcp-session-id": sessionId }), + ...(protocolVersion === null ? {} : { "mcp-protocol-version": protocolVersion }), + }); - const initializeId = "t3-acp-cli-initialize"; - const initialized = yield* send({ - jsonrpc: "2.0", - id: initializeId, - method: "initialize", - params: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: {}, - clientInfo: { name: "t3-code-acp-cli", version: "0.0.0" }, - }, + const send = Effect.fn("AcpMcpStdioBridge.send")(function* ( + message: unknown, + ): Effect.fn.Return, AcpMcpBridgeError> { + const response = yield* Effect.tryPromise({ + try: () => + fetchImplementation(options.endpoint, { + method: "POST", + headers: { "content-type": "application/json", ...headers() }, + body: JSON.stringify(message), + }), + catch: bridgeError, }); - const initializeResponse = initialized.find((entry) => asEnvelope(entry)?.id === initializeId); - if (initializeResponse === undefined || asEnvelope(initializeResponse)?.error !== undefined) { + sessionId = response.headers.get("mcp-session-id") ?? sessionId; + if (!response.ok) { + yield* discardResponseBody(response); return yield* Effect.fail( - new AcpMcpBridgeError("T3 Code MCP endpoint rejected initialization."), + new AcpMcpBridgeError(`T3 Code MCP endpoint responded with HTTP ${response.status}.`), ); } - yield* send({ jsonrpc: "2.0", method: "notifications/initialized" }); + const payloads = yield* Stream.runCollect(responsePayloads(response, bridgeError)); + for (const payload of payloads) { + protocolVersion = protocolVersionOf(payload) ?? protocolVersion; + } + return payloads; + }); - const callId = "t3-acp-cli-tool-call"; - const responses = yield* send({ - jsonrpc: "2.0", - id: callId, - method: "tools/call", - params: { name: options.tool, arguments: options.arguments }, - }); - const response = responses.find((entry) => asEnvelope(entry)?.id === callId); - const envelope = asEnvelope(response); + const request = Effect.fn("AcpMcpStdioBridge.request")(function* ( + method: string, + params: unknown, + ): Effect.fn.Return { + const id = `t3-cli-${nextId++}`; + const responses = yield* send({ jsonrpc: "2.0", id, method, params }); + const envelope = asEnvelope(responses.find((entry) => asEnvelope(entry)?.id === id)); if (envelope === null || envelope.error !== undefined) { return yield* Effect.fail( new AcpMcpBridgeError( - `T3 Code MCP tool call failed${envelope?.error === undefined ? "." : `: ${JSON.stringify(envelope.error)}`}`, + `T3 Code MCP ${method} failed${envelope?.error === undefined ? "." : `: ${JSON.stringify(envelope.error)}`}`, ), ); } - return envelope.result; + return envelope; + }); + + const terminate = Effect.suspend(() => + sessionId === null + ? Effect.void + : Effect.tryPromise(() => + fetchImplementation(options.endpoint, { method: "DELETE", headers: headers() }), + ).pipe(Effect.flatMap(discardResponseBody), Effect.ignore), + ); + + // Registered before the handshake so a session the server opened is ended + // even when the handshake fails, and the handshake stays interruptible. + return Effect.gen(function* () { + yield* Effect.addFinalizer(() => terminate); + yield* request("initialize", { + protocolVersion: MCP_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: { name: options.clientName, version: "0.0.0" }, + }); + yield* send({ jsonrpc: "2.0", method: "notifications/initialized" }); + return { + call: (tool, args) => + request("tools/call", { name: tool, arguments: args }).pipe( + Effect.map((envelope) => envelope.result), + ), + } satisfies McpToolSession; }); +}; + +/** + * This is the terminal fallback for ACP agents that accept `mcpServers` in + * `session/new` but fail to expose those tools to their model. Compliant ACP + * agents continue to use the stdio bridge above. + */ +function callAcpMcpTool(options: { + readonly endpoint: string; + readonly authorization: string; + readonly tool: string; + readonly arguments: Readonly>; +}): Effect.Effect { + return mcpToolSession({ ...options, clientName: "t3-code-acp-cli" }).pipe( + Effect.flatMap((session) => session.call(options.tool, options.arguments)), + Effect.scoped, + ); } export function runAcpMcpStdioBridge(options: AcpMcpStdioBridgeOptions): Effect.Effect { diff --git a/apps/server/src/mcp/toolkits/project/handlers.test.ts b/apps/server/src/mcp/toolkits/project/handlers.test.ts index aecc99f7dda2c..4c22393bf4761 100644 --- a/apps/server/src/mcp/toolkits/project/handlers.test.ts +++ b/apps/server/src/mcp/toolkits/project/handlers.test.ts @@ -4,6 +4,8 @@ import { ProjectId, ProviderInstanceId, type Project as ProjectRecord, + type ModelSelection, + type RuntimeMode, } from "@t3tools/contracts"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; @@ -28,7 +30,10 @@ import * as ProjectHandlers from "./handlers.ts"; import { ProjectToolkit } from "./tools.ts"; const clientLaunchHarness = (input: { - readonly runtimeModeCeiling: "approval-required" | "auto-accept-edits" | "auto" | "full-access"; + readonly runtimeModeCeiling: RuntimeMode; + readonly defaultRuntimeMode?: RuntimeMode; + /** Where Settings saves a project's model; the legacy project field stays null. */ + readonly overrideModel?: ModelSelection; readonly launched: Array; readonly workspaceRoot?: string; readonly checkWorktreeBase?: ThreadLaunch.ThreadLaunchService["Service"]["checkWorktreeBase"]; @@ -39,7 +44,19 @@ const clientLaunchHarness = (input: { model: "claude-opus", }; const layerDependencies = Layer.mergeAll( - ServerSettings.layerTest(), + ServerSettings.layerTest({ + ...(input.defaultRuntimeMode === undefined + ? {} + : { defaultRuntimeMode: input.defaultRuntimeMode }), + ...(input.overrideModel === undefined + ? {} + : { + projectSettingsFolded: true, + projectSettingsOverrides: { + [projectId]: { defaultModelSelection: input.overrideModel }, + }, + }), + }), NodeCrypto.layer, Layer.succeed(McpInvocationContext.McpInvocationContext, { environmentId: EnvironmentId.make("environment"), @@ -79,7 +96,7 @@ const clientLaunchHarness = (input: { ? Option.some({ id, workspaceRoot: input.workspaceRoot ?? "/projects/client-target", - defaultModelSelection: modelSelection, + defaultModelSelection: input.overrideModel === undefined ? modelSelection : null, } as unknown as ProjectRecord) : Option.none(), ), @@ -99,35 +116,85 @@ const clientLaunchHarness = (input: { return { projectId, modelSelection, dependencies: layerDependencies }; }; -it.effect("a client launches at its ceiling with the project's default model", () => - Effect.gen(function* () { - const launched: Array = []; - const { projectId, modelSelection, dependencies } = clientLaunchHarness({ - runtimeModeCeiling: "auto-accept-edits", - launched, - }); - const toolkit = yield* ProjectToolkit.pipe( - Effect.provide( - McpToolAccess.HandlersLayer.layer(ProjectHandlers.layer).pipe(Layer.provide(dependencies)), - ), - ); - const handle = (params: Parameters>[1]) => - toolkit - .handle("t3_thread_launch", params) - .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); +it.effect( + "a client launches with the project's default model and mode, capped at its ceiling", + () => + Effect.gen(function* () { + const launchWith = Effect.fn("test.launchWith")(function* ( + runtimeModeCeiling: RuntimeMode, + defaultRuntimeMode: RuntimeMode, + params: { + readonly title: string; + readonly message?: string; + readonly runtimeMode?: RuntimeMode; + readonly targeted?: false; + readonly overrideModel?: ModelSelection; + }, + ) { + const { targeted, overrideModel, ...input } = params; + const launched: Array = []; + const { projectId, modelSelection, dependencies } = clientLaunchHarness({ + runtimeModeCeiling, + defaultRuntimeMode, + launched, + ...(overrideModel === undefined ? {} : { overrideModel }), + }); + const toolkit = yield* ProjectToolkit.pipe( + Effect.provide( + McpToolAccess.HandlersLayer.layer(ProjectHandlers.layer).pipe( + Layer.provide(dependencies), + ), + ), + ); + const result = yield* toolkit + .handle("t3_thread_launch", targeted === false ? input : { ...input, projectId }) + .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies)); + return { result: result.at(-1)?.result, launched, projectId, modelSelection }; + }); - const result = yield* handle({ title: "Fix", projectId, message: "Fix the bug" }); - expect(result.at(-1)?.result).toMatchObject({ projectId, modelSelection }); - expect(launched[0]?.runtimeMode).toBe("auto-accept-edits"); - expect(launched[0]?.initialMessage?.senderThreadId).toBeUndefined(); + const belowCeiling = yield* launchWith("full-access", "approval-required", { + title: "Fix", + message: "Fix the bug", + }); + expect(belowCeiling.result).toMatchObject({ + projectId: belowCeiling.projectId, + modelSelection: belowCeiling.modelSelection, + }); + expect(belowCeiling.launched[0]?.runtimeMode).toBe("approval-required"); + expect(belowCeiling.launched[0]?.initialMessage?.senderThreadId).toBeUndefined(); - const escalated = yield* handle({ title: "Fix", projectId, runtimeMode: "full-access" }); - expect(escalated.at(-1)?.result).toMatchObject({ code: "runtime_mode_escalation_denied" }); + const aboveCeiling = yield* launchWith("auto-accept-edits", "full-access", { title: "Fix" }); + expect(aboveCeiling.launched[0]?.runtimeMode).toBe("auto-accept-edits"); - const untargeted = yield* handle({ title: "Fix" }); - expect(untargeted.at(-1)?.result).toMatchObject({ code: "target_required" }); - expect(launched).toHaveLength(1); - }), + const requested = yield* launchWith("full-access", "approval-required", { + title: "Fix", + runtimeMode: "auto", + }); + expect(requested.launched[0]?.runtimeMode).toBe("auto"); + + const escalated = yield* launchWith("auto-accept-edits", "auto-accept-edits", { + title: "Fix", + runtimeMode: "full-access", + }); + expect(escalated.result).toMatchObject({ code: "runtime_mode_escalation_denied" }); + expect(escalated.launched).toHaveLength(0); + + const untargeted = yield* launchWith("full-access", "full-access", { + title: "Fix", + targeted: false, + }); + expect(untargeted.result).toMatchObject({ code: "target_required" }); + + const overrideModel = { + instanceId: ProviderInstanceId.make("codex"), + model: "gpt-settings", + }; + const fromSettings = yield* launchWith("full-access", "full-access", { + title: "Fix", + overrideModel, + }); + expect(fromSettings.launched[0]?.modelSelection).toEqual(overrideModel); + }), ); it.effect("a launch binds only an existing checkout that is one of the project's worktrees", () => diff --git a/apps/server/src/mcp/toolkits/project/handlers.ts b/apps/server/src/mcp/toolkits/project/handlers.ts index 912d6ad127fc8..380a1b52ec963 100644 --- a/apps/server/src/mcp/toolkits/project/handlers.ts +++ b/apps/server/src/mcp/toolkits/project/handlers.ts @@ -9,6 +9,9 @@ import * as Project from "../../../project/ProjectService.ts"; import * as ManagedProjectFolders from "../../../project/ManagedProjectFolders.ts"; import * as Repositories from "../../../sourceControl/SourceControlRepositoryService.ts"; import * as GitVcsDriver from "../../../vcs/GitVcsDriver.ts"; +import * as ServerSettings from "../../../serverSettings.ts"; +import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; +import { resolveRuntimeMode } from "../../OrchestratorMcpService.ts"; import * as McpToolAccess from "../../McpToolAccess.ts"; import { newCommandId, readCaller, resolveProjectId, unavailable } from "../../threadAccess.ts"; import { ProjectToolkit } from "./tools.ts"; @@ -122,10 +125,26 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, { }), ), ); + // A caller outside a thread has nothing to inherit, so it starts with the + // project's effective defaults, as the composer does. Its mode stays + // within the ceiling it was approved with. + const projectDefaults = + caller === undefined + ? yield* ServerSettings.ServerSettingsService.pipe( + Effect.flatMap((settings) => settings.getSettings), + Effect.mapError(unavailable), + Effect.flatMap((settings) => + Effect.map( + readProject, + (project) => resolveProjectSettings(settings, projectId, project).settings, + ), + ), + ) + : undefined; const modelSelection = input.modelSelection ?? caller?.modelSelection ?? - (yield* readProject)?.defaultModelSelection ?? + projectDefaults?.defaultModelSelection ?? undefined; if (modelSelection === undefined) return yield* new OrchestratorMcpFailure({ @@ -133,13 +152,19 @@ export const layer = McpToolAccess.toLayer(ProjectToolkit, { message: "Pass modelSelection: the project has no default model. orchestrator_capabilities lists providers and models.", }); + const startedRuntimeMode = + projectDefaults !== undefined && input.runtimeMode === undefined + ? yield* resolveRuntimeMode(runtimeMode, projectDefaults.defaultRuntimeMode).pipe( + Effect.orElseSucceed(() => runtimeMode), + ) + : runtimeMode; const result = yield* ThreadMessageIntake.launchThread({ commandId, threadId, projectId, title: input.title, modelSelection, - runtimeMode, + runtimeMode: startedRuntimeMode, interactionMode, workspaceStrategy: input.workspaceStrategy ?? { type: "root" }, ...(input.message === undefined && attachments.length === 0 diff --git a/docs/user/outside-agents.md b/docs/user/outside-agents.md index 7cc6692e38f6c..1e7b80cde94cb 100644 --- a/docs/user/outside-agents.md +++ b/docs/user/outside-agents.md @@ -88,6 +88,28 @@ Any agent that supports remote MCP servers over HTTP with OAuth can connect. Giv it the MCP URL and choose OAuth, and the agent finds the sign-in settings itself. Agents without OAuth support cannot connect. +## Scripts on the host + +On the machine that runs the environment, `t3 thread` uses the same tools +without a sign-in: + +```sh +id=$(t3 thread start . "Find why the tests are slow") +t3 thread send "$id" "Now fix it" --wait +t3 thread show "$id" +t3 thread list +``` + +`.` means the project that contains the current folder; a project id also +works. A new thread uses the project's default model and permission mode, and +runs in the project folder unless you pass `--worktree `. `send` +never interrupts: a busy thread gets the message as its next turn. Pass `-` as +the message to read it from stdin. + +`--wait` prints the agent's reply. It exits 1 when the run fails or stops, and +2 when the agent waits for an answer you give in the app. Add `--json` for a +result that always includes the thread id. + ## Remove an agent Approved agents appear under **Settings → Connections** like other clients. From 7781e5209bde99e7dbaeb87b0209a99eef3c2ebf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 01:24:32 -0700 Subject: [PATCH 019/179] refactor(web): composer attachment tiles live in their own component (#22689) Co-authored-by: Claude Opus 5.5 --- apps/web/src/components/chat/ChatComposer.tsx | 495 ++-------------- .../chat/ComposerAttachmentStrip.tsx | 535 ++++++++++++++++++ 2 files changed, 589 insertions(+), 441 deletions(-) create mode 100644 apps/web/src/components/chat/ComposerAttachmentStrip.tsx diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index ad8c6603ec1f7..0d50637dd8a50 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -6,15 +6,12 @@ import { runtimeModeConfig, runtimeModeOptions as runtimeModes } from "./runtime import { isLocalEnvironmentDisabled } from "../../localEnvironment"; import { usePrimaryEnvironmentId } from "../../state/environments"; import { useRightPanelStore } from "~/rightPanelStore"; -import { AttachmentFilePreview } from "../files/AttachmentFilePreview"; -import { Dialog, DialogPopup, DialogTitle } from "../ui/dialog"; import { composerPullRequestsFromLinks, filterComposerPullRequestMatches, } from "@t3tools/shared/composerPullRequestMatches"; import { importPastedComposerText, readPastedComposerContext } from "../composerInlineTokenPaste"; import { elementContextToPreviewAnnotation } from "../../lib/elementContext"; -import { RefreshIcon } from "~/components/ui/refresh-icon"; import { questionAttachmentDraftId, countQuestionAttachments, @@ -41,7 +38,6 @@ import type { ScopedThreadRef, ServerProvider, ThreadId, - SnapShotSource, } from "@t3tools/contracts"; import { EnvironmentRequestInvalidError, @@ -69,7 +65,6 @@ import { createModelSelection, normalizeModelSlug } from "@t3tools/shared/model" import { USAGE_LIMITS_COMMAND } from "@t3tools/shared/usageLimits"; import { memo, - type ComponentProps, type ReactNode, useEffect, useEffectEvent, @@ -110,7 +105,6 @@ import { makeComposerMentionDragHandlers, } from "./composerMentionDrag"; import { - composerFloatingLayerProps, useComposerMenuProps, isInsideCollapsedComposerControls, isInsideRestingComposerControlScope, @@ -189,15 +183,11 @@ import { releaseAttachmentUpload, releaseDraftAttachment, releasePersistedAttachmentUpload, - retryAttachmentUpload, startAttachmentUpload, useAttachmentUploadStore, verifyStashedAttachmentUpload, } from "../../lib/attachmentUploadQueue"; -import { - attachmentUploadBlockReason, - formatAttachmentUploadProgress, -} from "../../lib/attachmentUploadState"; +import { attachmentUploadBlockReason } from "../../lib/attachmentUploadState"; import { isCommandPaletteOpen } from "../../commandPaletteBus"; import { getTerminalFocusOwner } from "../../lib/terminalFocus"; import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection"; @@ -297,6 +287,11 @@ import { import { ComposerPendingApprovalActions } from "./ComposerPendingApprovalActions"; import { CompactComposerControlsMenu } from "./CompactComposerControlsMenu"; import { ComposerImageThumbnail } from "./ComposerImageThumbnail"; +import { + ComposerAttachmentFileList, + ComposerAttachmentMediaRow, + ComposerFilePreviewDialog, +} from "./ComposerAttachmentStrip"; import { ComposerPrimaryActions } from "./ComposerPrimaryActions"; import { ComposerPendingApprovalPanel } from "./ComposerPendingApprovalPanel"; import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; @@ -331,21 +326,13 @@ import { shouldReserveContextWindowMeter, } from "./ContextWindowMeter.logic"; import { - attachVideoThumbnail, buildAttachmentVideoPreview, buildExpandedImagePreview, type ExpandedImagePreview, } from "./ExpandedImagePreview"; -import { - SNAP_SHOT_ATTACHMENT_FRAME_CLASS, - SnapShotAttachmentDetails, -} from "./SnapShotAttachmentDetails"; import { getPendingSnapShotAnimations, pendingSnapShotAnimationIdsForTarget, - scheduleSnapShotAnimationDestination, - setSnapShotAnimationDestination, - shouldAnimateSnapShotArrival, subscribeToPendingSnapShotAnimations, } from "../../lib/snapShotAnimation"; import { resizeSnapShotSource } from "../../lib/snapShotSource"; @@ -371,28 +358,8 @@ import { resetComposerScrollGesture, suppressActiveComposerScrollGesture, } from "./composerScrollGesture"; -import { prepareVideoFirstFrame } from "../../lib/videoFirstFrame"; import { observeResize } from "~/lib/observeResize"; -function ComposerVideoThumbnail({ file }: { file: File }) { - const setVideo = (video: HTMLVideoElement | null) => { - if (!video) return; - return attachVideoThumbnail(video, file); - }; - - return ( -