diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml
index e7966cb48c..6c632f7e07 100644
--- a/.github/actions/setup-bun/action.yml
+++ b/.github/actions/setup-bun/action.yml
@@ -1,10 +1,5 @@
name: "Setup Bun"
description: "Setup Bun with caching and install dependencies"
-inputs:
- cross-compile:
- description: "Pre-cache canary cross-compile binaries for all targets"
- required: false
- default: "false"
runs:
using: "composite"
steps:
@@ -21,12 +16,13 @@ runs:
shell: bash
run: |
if [ "$RUNNER_ARCH" = "X64" ]; then
+ V=$(node -p "require('./package.json').packageManager.split('@')[1]")
case "$RUNNER_OS" in
macOS) OS=darwin ;;
Linux) OS=linux ;;
Windows) OS=windows ;;
esac
- echo "url=https://github.com/oven-sh/bun/releases/download/canary/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
+ echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${V}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
fi
- name: Setup Bun
@@ -35,54 +31,6 @@ runs:
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
bun-download-url: ${{ steps.bun-url.outputs.url }}
- - name: Pre-cache canary cross-compile binaries
- if: inputs.cross-compile == 'true'
- shell: bash
- run: |
- BUN_VERSION=$(bun --revision)
- if echo "$BUN_VERSION" | grep -q "canary"; then
- SEMVER=$(echo "$BUN_VERSION" | sed 's/^\([0-9]*\.[0-9]*\.[0-9]*\).*/\1/')
- echo "Bun version: $BUN_VERSION (semver: $SEMVER)"
- CACHE_DIR="$HOME/.bun/install/cache"
- mkdir -p "$CACHE_DIR"
- TMP_DIR=$(mktemp -d)
- for TARGET in linux-aarch64 linux-x64 linux-x64-baseline linux-aarch64-musl linux-x64-musl linux-x64-musl-baseline darwin-aarch64 darwin-x64 windows-x64 windows-x64-baseline; do
- DEST="$CACHE_DIR/bun-${TARGET}-v${SEMVER}"
- if [ -f "$DEST" ]; then
- echo "Already cached: $DEST"
- continue
- fi
- URL="https://github.com/oven-sh/bun/releases/download/canary/bun-${TARGET}.zip"
- echo "Downloading $TARGET from $URL"
- if curl -sfL -o "$TMP_DIR/bun.zip" "$URL"; then
- unzip -qo "$TMP_DIR/bun.zip" -d "$TMP_DIR"
- if echo "$TARGET" | grep -q "windows"; then
- BIN_NAME="bun.exe"
- else
- BIN_NAME="bun"
- fi
- mv "$TMP_DIR/bun-${TARGET}/$BIN_NAME" "$DEST"
- chmod +x "$DEST"
- rm -rf "$TMP_DIR/bun-${TARGET}" "$TMP_DIR/bun.zip"
- echo "Cached: $DEST"
- # baseline bun resolves "bun-darwin-x64" to the baseline cache key
- # so copy the modern binary there too
- if [ "$TARGET" = "darwin-x64" ]; then
- BASELINE_DEST="$CACHE_DIR/bun-darwin-x64-baseline-v${SEMVER}"
- if [ ! -f "$BASELINE_DEST" ]; then
- cp "$DEST" "$BASELINE_DEST"
- echo "Cached (baseline alias): $BASELINE_DEST"
- fi
- fi
- else
- echo "Skipped: $TARGET (not available)"
- fi
- done
- rm -rf "$TMP_DIR"
- else
- echo "Not a canary build ($BUN_VERSION), skipping pre-cache"
- fi
-
- name: Install dependencies
run: bun install
shell: bash
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index cca7df5c4e..8d4c9038a7 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -77,8 +77,6 @@ jobs:
fetch-tags: true
- uses: ./.github/actions/setup-bun
- with:
- cross-compile: "true"
- name: Setup git committer
id: committer
@@ -90,7 +88,7 @@ jobs:
- name: Build
id: build
run: |
- ./packages/opencode/script/build.ts --all
+ ./packages/opencode/script/build.ts
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
diff --git a/.github/workflows/sign-cli.yml b/.github/workflows/sign-cli.yml
index 8917622317..d9d61fd800 100644
--- a/.github/workflows/sign-cli.yml
+++ b/.github/workflows/sign-cli.yml
@@ -20,12 +20,10 @@ jobs:
fetch-tags: true
- uses: ./.github/actions/setup-bun
- with:
- cross-compile: "true"
- name: Build
run: |
- ./packages/opencode/script/build.ts --all
+ ./packages/opencode/script/build.ts
- name: Upload unsigned Windows CLI
id: upload_unsigned_windows_cli
diff --git a/bun.lock b/bun.lock
index 2bdab5cb6a..f9f48eddd0 100644
--- a/bun.lock
+++ b/bun.lock
@@ -25,7 +25,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -75,7 +75,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -109,7 +109,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -136,7 +136,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -160,7 +160,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -184,7 +184,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -217,7 +217,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -246,7 +246,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -262,7 +262,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.2.14",
+ "version": "1.2.15",
"bin": {
"opencode": "./bin/opencode",
},
@@ -376,7 +376,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -396,7 +396,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.2.14",
+ "version": "1.2.15",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -407,7 +407,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -418,9 +418,30 @@
"typescript": "catalog:",
},
},
+ "packages/storybook": {
+ "name": "@opencode-ai/storybook",
+ "devDependencies": {
+ "@opencode-ai/ui": "workspace:*",
+ "@solidjs/meta": "catalog:",
+ "@storybook/addon-a11y": "^10.2.10",
+ "@storybook/addon-docs": "^10.2.10",
+ "@storybook/addon-links": "^10.2.10",
+ "@storybook/addon-onboarding": "^10.2.10",
+ "@storybook/addon-vitest": "^10.2.10",
+ "@tsconfig/node22": "catalog:",
+ "@types/node": "catalog:",
+ "@types/react": "18.0.25",
+ "react": "18.2.0",
+ "solid-js": "catalog:",
+ "storybook": "^10.2.10",
+ "storybook-solidjs-vite": "^10.0.9",
+ "typescript": "catalog:",
+ "vite": "catalog:",
+ },
+ },
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -462,7 +483,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"zod": "catalog:",
},
@@ -473,7 +494,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1136,6 +1157,8 @@
"@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
+ "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/vite-plugin-react-docgen-typescript@0.6.4", "", { "dependencies": { "glob": "^13.0.1", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA=="],
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -1208,6 +1231,8 @@
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
+ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
+
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
@@ -1302,6 +1327,8 @@
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
+ "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"],
+
"@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"],
"@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"],
@@ -1774,6 +1801,26 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+ "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="],
+
+ "@storybook/addon-docs": ["@storybook/addon-docs@10.2.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="],
+
+ "@storybook/addon-links": ["@storybook/addon-links@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" }, "optionalPeers": ["react"] }, "sha512-oo9Xx4/2OVJtptXKpqH4ySri7ZuBdiSOXlZVGejEfLa0Jeajlh/KIlREpGvzPPOqUVT7dSddWzBjJmJUyQC3ew=="],
+
+ "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.10", "", { "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-DkzZQTXHp99SpHMIQ5plbbHcs4EWVzWhLXlW+icA8sBlKo5Bwj540YcOApKbqB0m/OzWprsznwN7Kv4vfvHu4w=="],
+
+ "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.10", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-U2oHw+Ar+Xd06wDTB74VlujhIIW89OHThpJjwgqgM6NWrOC/XLllJ53ILFDyREBkMwpBD7gJQIoQpLEcKBIEhw=="],
+
+ "@storybook/builder-vite": ["@storybook/builder-vite@10.2.10", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="],
+
+ "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="],
+
+ "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
+
+ "@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="],
+
+ "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="],
+
"@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
"@swc/helpers": ["@swc/helpers@0.5.18", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="],
@@ -1866,6 +1913,12 @@
"@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
+ "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
+
+ "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
+
+ "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
+
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -1876,6 +1929,8 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+ "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
@@ -2010,7 +2065,7 @@
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
- "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
+ "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
"@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="],
@@ -2020,7 +2075,7 @@
"@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="],
- "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+ "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
"@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
@@ -2116,6 +2171,8 @@
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+ "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
+
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"astro": ["astro@5.7.13", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="],
@@ -2144,6 +2201,8 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
+ "axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
+
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -2258,7 +2317,7 @@
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
- "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
@@ -2274,6 +2333,8 @@
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
+ "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
+
"cheerio": ["cheerio@1.0.0-rc.12", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "htmlparser2": "^8.0.1", "parse5": "^7.0.0", "parse5-htmlparser2-tree-adapter": "^7.0.0" } }, "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q=="],
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
@@ -2368,6 +2429,8 @@
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+ "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
+
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
@@ -2388,6 +2451,8 @@
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
+ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
@@ -2440,6 +2505,8 @@
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
+ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
+
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -2834,6 +2901,8 @@
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
@@ -3074,6 +3143,8 @@
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
+
"lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
@@ -3084,6 +3155,8 @@
"luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
+ "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
@@ -3234,6 +3307,8 @@
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
+ "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
+
"miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="],
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
@@ -3426,6 +3501,8 @@
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
+
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
@@ -3492,6 +3569,8 @@
"pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="],
+ "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
+
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
@@ -3534,8 +3613,12 @@
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
+ "react-docgen-typescript": ["react-docgen-typescript@2.4.0", "", { "peerDependencies": { "typescript": ">= 4.3.x" } }, "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg=="],
+
"react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
+ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
+
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="],
@@ -3560,6 +3643,8 @@
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
+ "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
+
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
"recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
@@ -3568,6 +3653,8 @@
"recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="],
+ "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
+
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
@@ -3760,7 +3847,7 @@
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
- "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -3808,6 +3895,10 @@
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
+ "storybook": ["storybook@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg=="],
+
+ "storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="],
+
"stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="],
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
@@ -3834,6 +3925,8 @@
"strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
+ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
+
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
"strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
@@ -3896,6 +3989,8 @@
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
+ "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
+
"titleize": ["titleize@4.0.0", "", {}, "sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -3920,6 +4015,8 @@
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
+ "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
+
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -4020,6 +4117,8 @@
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
+ "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
+
"unstorage": ["unstorage@2.0.0-alpha.5", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
"unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="],
@@ -4032,6 +4131,8 @@
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
+ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
+
"utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
@@ -4106,6 +4207,8 @@
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+ "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
+
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
@@ -4260,6 +4363,8 @@
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="],
+ "@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
"@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@astrojs/solid-js/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
@@ -4488,6 +4593,8 @@
"@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
+ "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
@@ -4632,8 +4739,18 @@
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+ "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
+
+ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
+
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
+ "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
+
+ "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
+
+ "@vitest/mocker/@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -4692,8 +4809,6 @@
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
- "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
-
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
@@ -4714,6 +4829,8 @@
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
+ "estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
@@ -4814,6 +4931,10 @@
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
+ "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -4842,12 +4963,16 @@
"sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
- "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
-
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
+ "storybook/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+
+ "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
+
+ "storybook/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
+
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -4880,6 +5005,10 @@
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
+ "vitest/@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
+
+ "vitest/@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+
"vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"vitest/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
@@ -5210,6 +5339,8 @@
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+ "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
+
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
@@ -5304,6 +5435,60 @@
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+
+ "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+
+ "storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+
+ "storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+
+ "storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+
+ "storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+
+ "storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+
+ "storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+
+ "storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+
+ "storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+
+ "storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+
+ "storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+
+ "storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+
+ "storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+
+ "storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+
+ "storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+
+ "storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+
+ "storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+
+ "storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+
+ "storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+
+ "storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+
+ "storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+
+ "storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+
+ "storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+
+ "storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+
+ "storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+
+ "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
+
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
@@ -5372,6 +5557,8 @@
"vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
+ "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
"wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
diff --git a/nix/hashes.json b/nix/hashes.json
index 426f484f03..eaba0d8f0c 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,8 +1,8 @@
{
"nodeModules": {
- "x86_64-linux": "sha256-3hfy6nfEnGq4J6inH0pXANw05oas+81iuayn7J0pj9c=",
- "aarch64-linux": "sha256-dxWaLtzSeI5NfHwB6u0K10yxoA0ESz/r+zTEQ3FdKFY=",
- "aarch64-darwin": "sha256-kkK4rj4g0j2jJFXVmVH7CJcXlI8Dj/KmL/VC3iE4Z+8=",
- "x86_64-darwin": "sha256-jt51irxZd48kb0BItd8InP7lfsELUh0unVYO2es+a98="
+ "x86_64-linux": "sha256-dZoLhWe4smBsOF7WczMySLXSAB1YRO1vfhiOCL1rBf0=",
+ "aarch64-linux": "sha256-J7nIz1xuVZEHun5WRZkYRySz29B0A8g5g0RRxnIWTYU=",
+ "aarch64-darwin": "sha256-R2PuhX+EjUBuLE8MF0G0fcUwNaU+5n6V6uVeK89ulzw=",
+ "x86_64-darwin": "sha256-Bvzfz9TsTpYriZNLSLgpNcNb+BgtkgpjoWqdOtF2IBg="
}
}
diff --git a/package.json b/package.json
index 2e7c1172aa..3fd9f30667 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "AI-powered development tool",
"private": true,
"type": "module",
- "packageManager": "bun@1.3.9",
+ "packageManager": "bun@1.3.10",
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop tauri dev",
diff --git a/packages/app/e2e/projects/projects-switch.spec.ts b/packages/app/e2e/projects/projects-switch.spec.ts
index f17557a800..74b3890888 100644
--- a/packages/app/e2e/projects/projects-switch.spec.ts
+++ b/packages/app/e2e/projects/projects-switch.spec.ts
@@ -9,7 +9,7 @@ import {
sessionIDFromUrl,
} from "../actions"
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
-import { createSdk, dirSlug } from "../utils"
+import { createSdk, dirSlug, sessionPath } from "../utils"
function slugFromUrl(url: string) {
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
@@ -51,7 +51,6 @@ test("switching back to a project opens the latest workspace session", async ({
const other = await createTestProject()
const otherSlug = dirSlug(other)
- const stamp = Date.now()
let rootDir: string | undefined
let workspaceDir: string | undefined
let sessionID: string | undefined
@@ -80,6 +79,7 @@ test("switching back to a project opens the latest workspace session", async ({
const workspaceSlug = slugFromUrl(page.url())
workspaceDir = base64Decode(workspaceSlug)
+ if (!workspaceDir) throw new Error(`Failed to decode workspace slug: ${workspaceSlug}`)
await openSidebar(page)
const workspace = page.locator(workspaceItemSelector(workspaceSlug)).first()
@@ -92,15 +92,14 @@ test("switching back to a project opens the latest workspace session", async ({
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`))
- const prompt = page.locator(promptSelector)
- await expect(prompt).toBeVisible()
- await prompt.fill(`project switch remembers workspace ${stamp}`)
- await prompt.press("Enter")
-
- await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
- const created = sessionIDFromUrl(page.url())
- if (!created) throw new Error(`Failed to parse session id from URL: ${page.url()}`)
+ const created = await createSdk(workspaceDir)
+ .session.create()
+ .then((x) => x.data?.id)
+ if (!created) throw new Error(`Failed to create session for workspace: ${workspaceDir}`)
sessionID = created
+
+ await page.goto(sessionPath(workspaceDir, created))
+ await expect(page.locator(promptSelector)).toBeVisible()
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
await openSidebar(page)
@@ -114,7 +113,8 @@ test("switching back to a project opens the latest workspace session", async ({
await expect(rootButton).toBeVisible()
await rootButton.click()
- await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
+ await expect.poll(() => sessionIDFromUrl(page.url()) ?? "").toBe(created)
+ await expect(page).toHaveURL(new RegExp(`/session/${created}(?:[/?#]|$)`))
},
{ extra: [other] },
)
diff --git a/packages/app/package.json b/packages/app/package.json
index 37d2801baf..446c14e967 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.2.14",
+ "version": "1.2.15",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx
index af788d05b0..5ca29a520a 100644
--- a/packages/app/src/components/dialog-select-model-unpaid.tsx
+++ b/packages/app/src/components/dialog-select-model-unpaid.tsx
@@ -97,9 +97,20 @@ export const DialogSelectModelUnpaid: Component = () => {
{i.name}
+
+ {language.t("dialog.provider.opencode.tagline")}
+
{language.t("dialog.provider.tag.recommended")}
+
+ <>
+
+ {language.t("dialog.provider.opencodeGo.tagline")}
+
+ {language.t("dialog.provider.tag.recommended")}
+ >
+
{language.t("dialog.provider.anthropic.note")}
diff --git a/packages/app/src/components/dialog-select-provider.tsx b/packages/app/src/components/dialog-select-provider.tsx
index 8bbd3054b9..76e718bb00 100644
--- a/packages/app/src/components/dialog-select-provider.tsx
+++ b/packages/app/src/components/dialog-select-provider.tsx
@@ -29,6 +29,7 @@ export const DialogSelectProvider: Component = () => {
if (id === "anthropic") return language.t("dialog.provider.anthropic.note")
if (id === "openai") return language.t("dialog.provider.openai.note")
if (id.startsWith("github-copilot")) return language.t("dialog.provider.copilot.note")
+ if (id === "opencode-go") return language.t("dialog.provider.opencodeGo.tagline")
}
return (
@@ -70,6 +71,9 @@ export const DialogSelectProvider: Component = () => {
{i.name}
+
+ {language.t("dialog.provider.opencode.tagline")}
+
{language.t("settings.providers.tag.custom")}
@@ -77,6 +81,9 @@ export const DialogSelectProvider: Component = () => {
{language.t("dialog.provider.tag.recommended")}
{(value) => {value()}
}
+
+ {language.t("dialog.provider.tag.recommended")}
+
)}
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index adfd592f8d..9174133acd 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -1048,6 +1048,11 @@ export const PromptInput: Component
= (props) => {
}
const variants = createMemo(() => ["default", ...local.model.variant.list()])
+ const accepting = createMemo(() => {
+ const id = params.id
+ if (!id) return false
+ return permission.isAutoAccepting(id, sdk.directory)
+ })
return (
@@ -1233,7 +1238,9 @@ export const PromptInput: Component
= (props) => {
diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx
index b94e7a8e96..c1e2da7129 100644
--- a/packages/app/src/components/session/session-sortable-tab.tsx
+++ b/packages/app/src/components/session/session-sortable-tab.tsx
@@ -46,6 +46,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
title={language.t("common.closeTab")}
keybind={command.keybind("tab.close")}
placement="bottom"
+ gutter={10}
>
{
{item.name}
+
+
+ {language.t("dialog.provider.opencode.tagline")}
+
+
{language.t("dialog.provider.tag.recommended")}
+
+ <>
+
+ {language.t("dialog.provider.opencodeGo.tagline")}
+
+ {language.t("dialog.provider.tag.recommended")}
+ >
+
{(key) => {language.t(key())}}
diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx
index 988723834f..ccfda5e698 100644
--- a/packages/app/src/context/permission.tsx
+++ b/packages/app/src/context/permission.tsx
@@ -16,10 +16,6 @@ type PermissionRespondFn = (input: {
directory?: string
}) => void
-function shouldAutoAccept(perm: PermissionRequest) {
- return perm.permission === "edit"
-}
-
function isNonAllowRule(rule: unknown) {
if (!rule) return false
if (typeof rule === "string") return rule !== "allow"
@@ -40,10 +36,7 @@ function hasPermissionPromptRules(permission: unknown) {
if (Array.isArray(permission)) return false
const config = permission as Record
- if (isNonAllowRule(config.edit)) return true
- if (isNonAllowRule(config.write)) return true
-
- return false
+ return Object.values(config).some(isNonAllowRule)
}
export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
@@ -61,9 +54,25 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
})
const [store, setStore, _, ready] = persisted(
- Persist.global("permission", ["permission.v3"]),
+ {
+ ...Persist.global("permission", ["permission.v3"]),
+ migrate(value) {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value
+
+ const data = value as Record
+ if (data.autoAccept) return value
+
+ return {
+ ...data,
+ autoAccept:
+ typeof data.autoAcceptEdits === "object" && data.autoAcceptEdits && !Array.isArray(data.autoAcceptEdits)
+ ? data.autoAcceptEdits
+ : {},
+ }
+ },
+ },
createStore({
- autoAcceptEdits: {} as Record,
+ autoAccept: {} as Record,
}),
)
@@ -112,7 +121,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function isAutoAccepting(sessionID: string, directory?: string) {
const key = acceptKey(sessionID, directory)
- return store.autoAcceptEdits[key] ?? store.autoAcceptEdits[sessionID] ?? false
+ return store.autoAccept[key] ?? store.autoAccept[sessionID] ?? false
}
function bumpEnableVersion(sessionID: string, directory?: string) {
@@ -128,7 +137,6 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
const perm = event.properties
if (!isAutoAccepting(perm.sessionID, e.name)) return
- if (!shouldAutoAccept(perm)) return
respondOnce(perm, e.name)
})
@@ -139,8 +147,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
const version = bumpEnableVersion(sessionID, directory)
setStore(
produce((draft) => {
- draft.autoAcceptEdits[key] = true
- delete draft.autoAcceptEdits[sessionID]
+ draft.autoAccept[key] = true
+ delete draft.autoAccept[sessionID]
}),
)
@@ -152,7 +160,6 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
for (const perm of x.data ?? []) {
if (!perm?.id) continue
if (perm.sessionID !== sessionID) continue
- if (!shouldAutoAccept(perm)) continue
respondOnce(perm, directory)
}
})
@@ -164,8 +171,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
const key = directory ? acceptKey(sessionID, directory) : undefined
setStore(
produce((draft) => {
- if (key) delete draft.autoAcceptEdits[key]
- delete draft.autoAcceptEdits[sessionID]
+ if (key) delete draft.autoAccept[key]
+ delete draft.autoAccept[sessionID]
}),
)
}
@@ -174,7 +181,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
ready,
respond,
autoResponds(permission: PermissionRequest, directory?: string) {
- return isAutoAccepting(permission.sessionID, directory) && shouldAutoAccept(permission)
+ return isAutoAccepting(permission.sessionID, directory)
},
isAutoAccepting,
toggleAutoAccept(sessionID: string, directory: string) {
diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts
index 502364afdf..9ef5272ef5 100644
--- a/packages/app/src/hooks/use-providers.ts
+++ b/packages/app/src/hooks/use-providers.ts
@@ -3,7 +3,16 @@ import { decode64 } from "@/utils/base64"
import { useParams } from "@solidjs/router"
import { createMemo } from "solid-js"
-export const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"]
+export const popularProviders = [
+ "opencode",
+ "opencode-go",
+ "anthropic",
+ "github-copilot",
+ "openai",
+ "google",
+ "openrouter",
+ "vercel",
+]
const popularProviderSet = new Set(popularProviders)
export function useProviders() {
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index 91a16b3b85..0046a8bc45 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -65,8 +65,8 @@ export const dict = {
"command.model.variant.cycle.description": "التبديل إلى مستوى الجهد التالي",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا",
- "command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا",
+ "command.permissions.autoaccept.enable": "قبول الأذونات تلقائيًا",
+ "command.permissions.autoaccept.disable": "إيقاف قبول الأذونات تلقائيًا",
"command.workspace.toggle": "تبديل مساحات العمل",
"command.workspace.toggle.description": "تمكين أو تعطيل مساحات العمل المتعددة في الشريط الجانبي",
"command.session.undo": "تراجع",
@@ -91,6 +91,8 @@ export const dict = {
"dialog.provider.group.other": "آخر",
"dialog.provider.tag.recommended": "موصى به",
"dialog.provider.opencode.note": "نماذج مختارة تتضمن Claude و GPT و Gemini والمزيد",
+ "dialog.provider.opencode.tagline": "نماذج موثوقة ومحسنة",
+ "dialog.provider.opencodeGo.tagline": "اشتراك منخفض التكلفة للجميع",
"dialog.provider.anthropic.note": "اتصل باستخدام Claude Pro/Max أو مفتاح API",
"dialog.provider.copilot.note": "اتصل باستخدام Copilot أو مفتاح API",
"dialog.provider.openai.note": "اتصل باستخدام ChatGPT Pro/Plus أو مفتاح API",
@@ -364,10 +366,10 @@ export const dict = {
"toast.workspace.enabled.description": "الآن يتم عرض عدة worktrees في الشريط الجانبي",
"toast.workspace.disabled.title": "تم تعطيل مساحات العمل",
"toast.workspace.disabled.description": "يتم عرض worktree الرئيسي فقط في الشريط الجانبي",
- "toast.permissions.autoaccept.on.title": "قبول التعديلات تلقائيًا",
- "toast.permissions.autoaccept.on.description": "سيتم الموافقة تلقائيًا على أذونات التحرير والكتابة",
- "toast.permissions.autoaccept.off.title": "توقف قبول التعديلات تلقائيًا",
- "toast.permissions.autoaccept.off.description": "ستتطلب أذونات التحرير والكتابة موافقة",
+ "toast.permissions.autoaccept.on.title": "يتم قبول الأذونات تلقائيًا",
+ "toast.permissions.autoaccept.on.description": "ستتم الموافقة على طلبات الأذونات تلقائيًا",
+ "toast.permissions.autoaccept.off.title": "تم إيقاف قبول الأذونات تلقائيًا",
+ "toast.permissions.autoaccept.off.description": "ستتطلب طلبات الأذونات موافقة",
"toast.model.none.title": "لم يتم تحديد نموذج",
"toast.model.none.description": "قم بتوصيل موفر لتلخيص هذه الجلسة",
"toast.file.loadFailed.title": "فشل تحميل الملف",
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index 7682a12b69..0d41ba7fca 100644
--- a/packages/app/src/i18n/br.ts
+++ b/packages/app/src/i18n/br.ts
@@ -65,8 +65,8 @@ export const dict = {
"command.model.variant.cycle.description": "Mudar para o próximo nível de esforço",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Aceitar edições automaticamente",
- "command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente",
+ "command.permissions.autoaccept.enable": "Aceitar permissões automaticamente",
+ "command.permissions.autoaccept.disable": "Parar de aceitar permissões automaticamente",
"command.workspace.toggle": "Alternar espaços de trabalho",
"command.workspace.toggle.description": "Habilitar ou desabilitar múltiplos espaços de trabalho na barra lateral",
"command.session.undo": "Desfazer",
@@ -91,6 +91,8 @@ export const dict = {
"dialog.provider.group.other": "Outro",
"dialog.provider.tag.recommended": "Recomendado",
"dialog.provider.opencode.note": "Modelos selecionados incluindo Claude, GPT, Gemini e mais",
+ "dialog.provider.opencode.tagline": "Modelos otimizados e confiáveis",
+ "dialog.provider.opencodeGo.tagline": "Assinatura de baixo custo para todos",
"dialog.provider.anthropic.note": "Conectar com Claude Pro/Max ou chave de API",
"dialog.provider.copilot.note": "Conectar com Copilot ou chave de API",
"dialog.provider.openai.note": "Conectar com ChatGPT Pro/Plus ou chave de API",
@@ -365,10 +367,10 @@ export const dict = {
"toast.workspace.enabled.description": "Várias worktrees agora são exibidas na barra lateral",
"toast.workspace.disabled.title": "Espaços de trabalho desativados",
"toast.workspace.disabled.description": "Apenas a worktree principal é exibida na barra lateral",
- "toast.permissions.autoaccept.on.title": "Aceitando edições automaticamente",
- "toast.permissions.autoaccept.on.description": "Permissões de edição e escrita serão aprovadas automaticamente",
- "toast.permissions.autoaccept.off.title": "Parou de aceitar edições automaticamente",
- "toast.permissions.autoaccept.off.description": "Permissões de edição e escrita exigirão aprovação",
+ "toast.permissions.autoaccept.on.title": "Aceitando permissões automaticamente",
+ "toast.permissions.autoaccept.on.description": "Solicitações de permissão serão aprovadas automaticamente",
+ "toast.permissions.autoaccept.off.title": "Parou de aceitar permissões automaticamente",
+ "toast.permissions.autoaccept.off.description": "Solicitações de permissão exigirão aprovação",
"toast.model.none.title": "Nenhum modelo selecionado",
"toast.model.none.description": "Conecte um provedor para resumir esta sessão",
"toast.file.loadFailed.title": "Falha ao carregar arquivo",
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index cb0274042e..a34d857b96 100644
--- a/packages/app/src/i18n/bs.ts
+++ b/packages/app/src/i18n/bs.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "Prebaci na sljedeći nivo",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Automatski prihvataj izmjene",
- "command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena",
+ "command.permissions.autoaccept.enable": "Automatski prihvati dozvole",
+ "command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje dozvola",
"command.workspace.toggle": "Prikaži/sakrij radne prostore",
"command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci",
"command.session.undo": "Poništi",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "Ostalo",
"dialog.provider.tag.recommended": "Preporučeno",
"dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge",
+ "dialog.provider.opencode.tagline": "Pouzdani optimizovani modeli",
+ "dialog.provider.opencodeGo.tagline": "Povoljna pretplata za sve",
"dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max",
"dialog.provider.copilot.note": "AI modeli za pomoć pri kodiranju putem GitHub Copilot",
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke",
@@ -403,10 +405,10 @@ export const dict = {
"toast.workspace.disabled.title": "Radni prostori onemogućeni",
"toast.workspace.disabled.description": "Samo glavni worktree se prikazuje u bočnoj traci",
- "toast.permissions.autoaccept.on.title": "Automatsko prihvatanje izmjena",
- "toast.permissions.autoaccept.on.description": "Dozvole za izmjene i pisanje biće automatski odobrene",
- "toast.permissions.autoaccept.off.title": "Zaustavljeno automatsko prihvatanje izmjena",
- "toast.permissions.autoaccept.off.description": "Dozvole za izmjene i pisanje zahtijevaće odobrenje",
+ "toast.permissions.autoaccept.on.title": "Automatsko prihvatanje dozvola",
+ "toast.permissions.autoaccept.on.description": "Zahtjevi za dozvole će biti automatski odobreni",
+ "toast.permissions.autoaccept.off.title": "Zaustavljeno automatsko prihvatanje dozvola",
+ "toast.permissions.autoaccept.off.description": "Zahtjevi za dozvole će zahtijevati odobrenje",
"toast.model.none.title": "Nije odabran model",
"toast.model.none.description": "Poveži provajdera da sažmeš ovu sesiju",
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index 30cc555eb1..3df23bd433 100644
--- a/packages/app/src/i18n/da.ts
+++ b/packages/app/src/i18n/da.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "Skift til næste indsatsniveau",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Accepter ændringer automatisk",
- "command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer",
+ "command.permissions.autoaccept.enable": "Accepter tilladelser automatisk",
+ "command.permissions.autoaccept.disable": "Stop med at acceptere tilladelser automatisk",
"command.workspace.toggle": "Skift arbejdsområder",
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbejdsområder i sidebjælken",
"command.session.undo": "Fortryd",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "Andre",
"dialog.provider.tag.recommended": "Anbefalet",
"dialog.provider.opencode.note": "Udvalgte modeller inklusive Claude, GPT, Gemini og flere",
+ "dialog.provider.opencode.tagline": "Pålidelige optimerede modeller",
+ "dialog.provider.opencodeGo.tagline": "Billigt abonnement for alle",
"dialog.provider.anthropic.note": "Direkte adgang til Claude-modeller, inklusive Pro og Max",
"dialog.provider.copilot.note": "AI-modeller til kodningsassistance via GitHub Copilot",
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
@@ -396,10 +398,10 @@ export const dict = {
"toast.theme.title": "Tema skiftet",
"toast.scheme.title": "Farveskema",
- "toast.permissions.autoaccept.on.title": "Accepterer ændringer automatisk",
- "toast.permissions.autoaccept.on.description": "Redigerings- og skrivetilladelser vil automatisk blive godkendt",
- "toast.permissions.autoaccept.off.title": "Stoppede automatisk accept af ændringer",
- "toast.permissions.autoaccept.off.description": "Redigerings- og skrivetilladelser vil kræve godkendelse",
+ "toast.permissions.autoaccept.on.title": "Accepterer tilladelser automatisk",
+ "toast.permissions.autoaccept.on.description": "Anmodninger om tilladelse godkendes automatisk",
+ "toast.permissions.autoaccept.off.title": "Stoppet med at acceptere tilladelser automatisk",
+ "toast.permissions.autoaccept.off.description": "Anmodninger om tilladelse vil kræve godkendelse",
"toast.workspace.enabled.title": "Arbejdsområder aktiveret",
"toast.workspace.enabled.description": "Flere worktrees vises nu i sidepanelet",
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index 3a7bbe9277..ce48a19534 100644
--- a/packages/app/src/i18n/de.ts
+++ b/packages/app/src/i18n/de.ts
@@ -69,8 +69,8 @@ export const dict = {
"command.model.variant.cycle.description": "Zum nächsten Aufwandslevel wechseln",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren",
- "command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen",
+ "command.permissions.autoaccept.enable": "Berechtigungen automatisch akzeptieren",
+ "command.permissions.autoaccept.disable": "Automatische Akzeptanz von Berechtigungen stoppen",
"command.workspace.toggle": "Arbeitsbereiche umschalten",
"command.workspace.toggle.description": "Mehrere Arbeitsbereiche in der Seitenleiste aktivieren oder deaktivieren",
"command.session.undo": "Rückgängig",
@@ -95,6 +95,8 @@ export const dict = {
"dialog.provider.group.other": "Andere",
"dialog.provider.tag.recommended": "Empfohlen",
"dialog.provider.opencode.note": "Kuratierte Modelle inklusive Claude, GPT, Gemini und mehr",
+ "dialog.provider.opencode.tagline": "Zuverlässige, optimierte Modelle",
+ "dialog.provider.opencodeGo.tagline": "Kostengünstiges Abo für alle",
"dialog.provider.anthropic.note": "Mit Claude Pro/Max oder API-Schlüssel verbinden",
"dialog.provider.copilot.note": "Mit Copilot oder API-Schlüssel verbinden",
"dialog.provider.openai.note": "Mit ChatGPT Pro/Plus oder API-Schlüssel verbinden",
@@ -372,10 +374,10 @@ export const dict = {
"toast.workspace.enabled.description": "Mehrere Worktrees werden jetzt in der Seitenleiste angezeigt",
"toast.workspace.disabled.title": "Arbeitsbereiche deaktiviert",
"toast.workspace.disabled.description": "Nur der Haupt-Worktree wird in der Seitenleiste angezeigt",
- "toast.permissions.autoaccept.on.title": "Änderungen werden automatisch akzeptiert",
- "toast.permissions.autoaccept.on.description": "Bearbeitungs- und Schreibrechte werden automatisch genehmigt",
- "toast.permissions.autoaccept.off.title": "Automatische Annahme von Änderungen gestoppt",
- "toast.permissions.autoaccept.off.description": "Bearbeitungs- und Schreibrechte erfordern Genehmigung",
+ "toast.permissions.autoaccept.on.title": "Berechtigungen werden automatisch akzeptiert",
+ "toast.permissions.autoaccept.on.description": "Berechtigungsanfragen werden automatisch genehmigt",
+ "toast.permissions.autoaccept.off.title": "Automatische Akzeptanz von Berechtigungen gestoppt",
+ "toast.permissions.autoaccept.off.description": "Berechtigungsanfragen erfordern eine Genehmigung",
"toast.model.none.title": "Kein Modell ausgewählt",
"toast.model.none.description": "Verbinden Sie einen Anbieter, um diese Sitzung zusammenzufassen",
"toast.file.loadFailed.title": "Datei konnte nicht geladen werden",
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 0b4388ceb1..0b7a2e2808 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "Switch to the next effort level",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Auto-accept edits",
- "command.permissions.autoaccept.disable": "Stop auto-accepting edits",
+ "command.permissions.autoaccept.enable": "Auto-accept permissions",
+ "command.permissions.autoaccept.disable": "Stop auto-accepting permissions",
"command.workspace.toggle": "Toggle workspaces",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
"command.session.undo": "Undo",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "Other",
"dialog.provider.tag.recommended": "Recommended",
"dialog.provider.opencode.note": "Curated models including Claude, GPT, Gemini and more",
+ "dialog.provider.opencode.tagline": "Reliable optimized models",
+ "dialog.provider.opencodeGo.tagline": "Low cost subscription for everyone",
"dialog.provider.anthropic.note": "Direct access to Claude models, including Pro and Max",
"dialog.provider.copilot.note": "AI models for coding assistance via GitHub Copilot",
"dialog.provider.openai.note": "GPT models for fast, capable general AI tasks",
@@ -402,10 +404,10 @@ export const dict = {
"toast.workspace.disabled.title": "Workspaces disabled",
"toast.workspace.disabled.description": "Only the main worktree is shown in the sidebar",
- "toast.permissions.autoaccept.on.title": "Auto-accepting edits",
- "toast.permissions.autoaccept.on.description": "Edit and write permissions will be automatically approved",
- "toast.permissions.autoaccept.off.title": "Stopped auto-accepting edits",
- "toast.permissions.autoaccept.off.description": "Edit and write permissions will require approval",
+ "toast.permissions.autoaccept.on.title": "Auto-accepting permissions",
+ "toast.permissions.autoaccept.on.description": "Permission requests will be automatically approved",
+ "toast.permissions.autoaccept.off.title": "Stopped auto-accepting permissions",
+ "toast.permissions.autoaccept.off.description": "Permission requests will require approval",
"toast.model.none.title": "No model selected",
"toast.model.none.description": "Connect a provider to summarize this session",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index 3566226d7b..de490cbe90 100644
--- a/packages/app/src/i18n/es.ts
+++ b/packages/app/src/i18n/es.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "Cambiar al siguiente nivel de esfuerzo",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente",
- "command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente",
+ "command.permissions.autoaccept.enable": "Aceptar permisos automáticamente",
+ "command.permissions.autoaccept.disable": "Dejar de aceptar permisos automáticamente",
"command.workspace.toggle": "Alternar espacios de trabajo",
"command.workspace.toggle.description": "Habilitar o deshabilitar múltiples espacios de trabajo en la barra lateral",
"command.session.undo": "Deshacer",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "Otro",
"dialog.provider.tag.recommended": "Recomendado",
"dialog.provider.opencode.note": "Modelos seleccionados incluyendo Claude, GPT, Gemini y más",
+ "dialog.provider.opencode.tagline": "Modelos optimizados y fiables",
+ "dialog.provider.opencodeGo.tagline": "Suscripción económica para todos",
"dialog.provider.anthropic.note": "Acceso directo a modelos Claude, incluyendo Pro y Max",
"dialog.provider.copilot.note": "Modelos de IA para asistencia de codificación a través de GitHub Copilot",
"dialog.provider.openai.note": "Modelos GPT para tareas de IA generales rápidas y capaces",
@@ -403,10 +405,10 @@ export const dict = {
"toast.workspace.disabled.title": "Espacios de trabajo deshabilitados",
"toast.workspace.disabled.description": "Solo se muestra el worktree principal en la barra lateral",
- "toast.permissions.autoaccept.on.title": "Aceptando ediciones automáticamente",
- "toast.permissions.autoaccept.on.description": "Los permisos de edición y escritura serán aprobados automáticamente",
- "toast.permissions.autoaccept.off.title": "Se dejó de aceptar ediciones automáticamente",
- "toast.permissions.autoaccept.off.description": "Los permisos de edición y escritura requerirán aprobación",
+ "toast.permissions.autoaccept.on.title": "Aceptando permisos automáticamente",
+ "toast.permissions.autoaccept.on.description": "Las solicitudes de permisos se aprobarán automáticamente",
+ "toast.permissions.autoaccept.off.title": "Se dejó de aceptar permisos automáticamente",
+ "toast.permissions.autoaccept.off.description": "Las solicitudes de permisos requerirán aprobación",
"toast.model.none.title": "Ningún modelo seleccionado",
"toast.model.none.description": "Conecta un proveedor para resumir esta sesión",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index c961f060e1..5e197b4fb4 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -65,8 +65,8 @@ export const dict = {
"command.model.variant.cycle.description": "Passer au niveau d'effort suivant",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Accepter automatiquement les modifications",
- "command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications",
+ "command.permissions.autoaccept.enable": "Accepter automatiquement les permissions",
+ "command.permissions.autoaccept.disable": "Arrêter d'accepter automatiquement les permissions",
"command.workspace.toggle": "Basculer les espaces de travail",
"command.workspace.toggle.description": "Activer ou désactiver plusieurs espaces de travail dans la barre latérale",
"command.session.undo": "Annuler",
@@ -91,6 +91,8 @@ export const dict = {
"dialog.provider.group.other": "Autre",
"dialog.provider.tag.recommended": "Recommandé",
"dialog.provider.opencode.note": "Modèles sélectionnés incluant Claude, GPT, Gemini et plus",
+ "dialog.provider.opencode.tagline": "Modèles optimisés et fiables",
+ "dialog.provider.opencodeGo.tagline": "Abonnement abordable pour tous",
"dialog.provider.anthropic.note": "Connectez-vous avec Claude Pro/Max ou une clé API",
"dialog.provider.copilot.note": "Connectez-vous avec Copilot ou une clé API",
"dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clé API",
@@ -366,12 +368,10 @@ export const dict = {
"toast.workspace.enabled.description": "Plusieurs worktrees sont désormais affichés dans la barre latérale",
"toast.workspace.disabled.title": "Espaces de travail désactivés",
"toast.workspace.disabled.description": "Seul le worktree principal est affiché dans la barre latérale",
- "toast.permissions.autoaccept.on.title": "Acceptation auto des modifications",
- "toast.permissions.autoaccept.on.description":
- "Les permissions de modification et d'écriture seront automatiquement approuvées",
- "toast.permissions.autoaccept.off.title": "Arrêt acceptation auto des modifications",
- "toast.permissions.autoaccept.off.description":
- "Les permissions de modification et d'écriture nécessiteront une approbation",
+ "toast.permissions.autoaccept.on.title": "Acceptation automatique des permissions",
+ "toast.permissions.autoaccept.on.description": "Les demandes de permission seront approuvées automatiquement",
+ "toast.permissions.autoaccept.off.title": "Acceptation automatique des permissions arrêtée",
+ "toast.permissions.autoaccept.off.description": "Les demandes de permission nécessiteront une approbation",
"toast.model.none.title": "Aucun modèle sélectionné",
"toast.model.none.description": "Connectez un fournisseur pour résumer cette session",
"toast.file.loadFailed.title": "Échec du chargement du fichier",
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index 7a62c9de27..30f27c197d 100644
--- a/packages/app/src/i18n/ja.ts
+++ b/packages/app/src/i18n/ja.ts
@@ -65,8 +65,8 @@ export const dict = {
"command.model.variant.cycle.description": "次の思考レベルに切り替え",
"command.prompt.mode.shell": "シェル",
"command.prompt.mode.normal": "プロンプト",
- "command.permissions.autoaccept.enable": "編集を自動承認",
- "command.permissions.autoaccept.disable": "編集の自動承認を停止",
+ "command.permissions.autoaccept.enable": "権限を自動承認する",
+ "command.permissions.autoaccept.disable": "権限の自動承認を停止する",
"command.workspace.toggle": "ワークスペースを切り替え",
"command.workspace.toggle.description": "サイドバーでの複数のワークスペースの有効化・無効化",
"command.session.undo": "元に戻す",
@@ -91,6 +91,8 @@ export const dict = {
"dialog.provider.group.other": "その他",
"dialog.provider.tag.recommended": "推奨",
"dialog.provider.opencode.note": "Claude, GPT, Geminiなどを含む厳選されたモデル",
+ "dialog.provider.opencode.tagline": "信頼性の高い最適化モデル",
+ "dialog.provider.opencodeGo.tagline": "すべての人に低価格のサブスクリプション",
"dialog.provider.anthropic.note": "Claude Pro/MaxまたはAPIキーで接続",
"dialog.provider.copilot.note": "CopilotまたはAPIキーで接続",
"dialog.provider.openai.note": "ChatGPT Pro/PlusまたはAPIキーで接続",
@@ -364,10 +366,10 @@ export const dict = {
"toast.workspace.enabled.description": "サイドバーに複数のワークツリーが表示されます",
"toast.workspace.disabled.title": "ワークスペースが無効になりました",
"toast.workspace.disabled.description": "サイドバーにはメインのワークツリーのみが表示されます",
- "toast.permissions.autoaccept.on.title": "編集を自動承認中",
- "toast.permissions.autoaccept.on.description": "編集と書き込みの権限は自動的に承認されます",
- "toast.permissions.autoaccept.off.title": "編集の自動承認を停止しました",
- "toast.permissions.autoaccept.off.description": "編集と書き込みの権限には承認が必要です",
+ "toast.permissions.autoaccept.on.title": "権限を自動承認しています",
+ "toast.permissions.autoaccept.on.description": "権限の要求は自動的に承認されます",
+ "toast.permissions.autoaccept.off.title": "権限の自動承認を停止しました",
+ "toast.permissions.autoaccept.off.description": "権限の要求には承認が必要になります",
"toast.model.none.title": "モデルが選択されていません",
"toast.model.none.description": "このセッションを要約するにはプロバイダーを接続してください",
"toast.file.loadFailed.title": "ファイルの読み込みに失敗しました",
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index 8967c71cff..da6cde9eab 100644
--- a/packages/app/src/i18n/ko.ts
+++ b/packages/app/src/i18n/ko.ts
@@ -69,8 +69,8 @@ export const dict = {
"command.model.variant.cycle.description": "다음 생각 수준으로 전환",
"command.prompt.mode.shell": "셸",
"command.prompt.mode.normal": "프롬프트",
- "command.permissions.autoaccept.enable": "편집 자동 수락",
- "command.permissions.autoaccept.disable": "편집 자동 수락 중지",
+ "command.permissions.autoaccept.enable": "권한 자동 수락",
+ "command.permissions.autoaccept.disable": "권한 자동 수락 중지",
"command.workspace.toggle": "작업 공간 전환",
"command.workspace.toggle.description": "사이드바에서 다중 작업 공간 활성화 또는 비활성화",
"command.session.undo": "실행 취소",
@@ -95,6 +95,8 @@ export const dict = {
"dialog.provider.group.other": "기타",
"dialog.provider.tag.recommended": "추천",
"dialog.provider.opencode.note": "Claude, GPT, Gemini 등을 포함한 엄선된 모델",
+ "dialog.provider.opencode.tagline": "신뢰할 수 있는 최적화 모델",
+ "dialog.provider.opencodeGo.tagline": "모두를 위한 저렴한 구독",
"dialog.provider.anthropic.note": "Claude Pro/Max 또는 API 키로 연결",
"dialog.provider.copilot.note": "Copilot 또는 API 키로 연결",
"dialog.provider.openai.note": "ChatGPT Pro/Plus 또는 API 키로 연결",
@@ -367,10 +369,10 @@ export const dict = {
"toast.workspace.enabled.description": "이제 사이드바에 여러 작업 트리가 표시됩니다",
"toast.workspace.disabled.title": "작업 공간 비활성화됨",
"toast.workspace.disabled.description": "사이드바에 메인 작업 트리만 표시됩니다",
- "toast.permissions.autoaccept.on.title": "편집 자동 수락 중",
- "toast.permissions.autoaccept.on.description": "편집 및 쓰기 권한이 자동으로 승인됩니다",
- "toast.permissions.autoaccept.off.title": "편집 자동 수락 중지됨",
- "toast.permissions.autoaccept.off.description": "편집 및 쓰기 권한 승인이 필요합니다",
+ "toast.permissions.autoaccept.on.title": "권한 자동 수락 중",
+ "toast.permissions.autoaccept.on.description": "권한 요청이 자동으로 승인됩니다",
+ "toast.permissions.autoaccept.off.title": "권한 자동 수락 중지됨",
+ "toast.permissions.autoaccept.off.description": "권한 요청에 승인이 필요합니다",
"toast.model.none.title": "선택된 모델 없음",
"toast.model.none.description": "이 세션을 요약하려면 공급자를 연결하세요",
"toast.file.loadFailed.title": "파일 로드 실패",
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index 3fbe75716d..bc04695d30 100644
--- a/packages/app/src/i18n/no.ts
+++ b/packages/app/src/i18n/no.ts
@@ -74,8 +74,8 @@ export const dict = {
"command.model.variant.cycle.description": "Bytt til neste innsatsnivå",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Godta endringer automatisk",
- "command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk",
+ "command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
+ "command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
"command.workspace.toggle": "Veksle arbeidsområder",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
"command.session.undo": "Angre",
@@ -102,6 +102,8 @@ export const dict = {
"dialog.provider.group.other": "Andre",
"dialog.provider.tag.recommended": "Anbefalt",
"dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og mer",
+ "dialog.provider.opencode.tagline": "Pålitelige, optimaliserte modeller",
+ "dialog.provider.opencodeGo.tagline": "Rimelig abonnement for alle",
"dialog.provider.anthropic.note": "Direkte tilgang til Claude-modeller, inkludert Pro og Max",
"dialog.provider.copilot.note": "AI-modeller for kodeassistanse via GitHub Copilot",
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
@@ -404,10 +406,10 @@ export const dict = {
"toast.workspace.disabled.title": "Arbeidsområder deaktivert",
"toast.workspace.disabled.description": "Kun hoved-worktree vises i sidefeltet",
- "toast.permissions.autoaccept.on.title": "Godtar endringer automatisk",
- "toast.permissions.autoaccept.on.description": "Redigerings- og skrivetillatelser vil bli godkjent automatisk",
- "toast.permissions.autoaccept.off.title": "Sluttet å godta endringer automatisk",
- "toast.permissions.autoaccept.off.description": "Redigerings- og skrivetillatelser vil kreve godkjenning",
+ "toast.permissions.autoaccept.on.title": "Aksepterer tillatelser automatisk",
+ "toast.permissions.autoaccept.on.description": "Forespørsler om tillatelse vil bli godkjent automatisk",
+ "toast.permissions.autoaccept.off.title": "Stoppet automatisk akseptering av tillatelser",
+ "toast.permissions.autoaccept.off.description": "Forespørsler om tillatelse vil kreve godkjenning",
"toast.model.none.title": "Ingen modell valgt",
"toast.model.none.description": "Koble til en leverandør for å oppsummere denne sesjonen",
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index d8ae150d7c..0be46f095c 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -65,8 +65,8 @@ export const dict = {
"command.model.variant.cycle.description": "Przełącz na następny poziom wysiłku",
"command.prompt.mode.shell": "Terminal",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji",
- "command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji",
+ "command.permissions.autoaccept.enable": "Automatycznie akceptuj uprawnienia",
+ "command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie uprawnień",
"command.workspace.toggle": "Przełącz przestrzenie robocze",
"command.workspace.toggle.description": "Włącz lub wyłącz wiele przestrzeni roboczych na pasku bocznym",
"command.session.undo": "Cofnij",
@@ -91,6 +91,8 @@ export const dict = {
"dialog.provider.group.other": "Inne",
"dialog.provider.tag.recommended": "Zalecane",
"dialog.provider.opencode.note": "Wyselekcjonowane modele, w tym Claude, GPT, Gemini i inne",
+ "dialog.provider.opencode.tagline": "Niezawodne, zoptymalizowane modele",
+ "dialog.provider.opencodeGo.tagline": "Tania subskrypcja dla każdego",
"dialog.provider.anthropic.note": "Bezpośredni dostęp do modeli Claude, w tym Pro i Max",
"dialog.provider.copilot.note": "Modele AI do pomocy w kodowaniu przez GitHub Copilot",
"dialog.provider.openai.note": "Modele GPT do szybkich i wszechstronnych zadań AI",
@@ -365,10 +367,10 @@ export const dict = {
"toast.workspace.enabled.description": "Kilka worktree jest teraz wyświetlanych na pasku bocznym",
"toast.workspace.disabled.title": "Przestrzenie robocze wyłączone",
"toast.workspace.disabled.description": "Tylko główny worktree jest wyświetlany na pasku bocznym",
- "toast.permissions.autoaccept.on.title": "Automatyczne akceptowanie edycji",
- "toast.permissions.autoaccept.on.description": "Uprawnienia do edycji i zapisu będą automatycznie zatwierdzane",
- "toast.permissions.autoaccept.off.title": "Zatrzymano automatyczne akceptowanie edycji",
- "toast.permissions.autoaccept.off.description": "Uprawnienia do edycji i zapisu będą wymagały zatwierdzenia",
+ "toast.permissions.autoaccept.on.title": "Automatyczne akceptowanie uprawnień",
+ "toast.permissions.autoaccept.on.description": "Żądania uprawnień będą automatycznie zatwierdzane",
+ "toast.permissions.autoaccept.off.title": "Zatrzymano automatyczne akceptowanie uprawnień",
+ "toast.permissions.autoaccept.off.description": "Żądania uprawnień będą wymagały zatwierdzenia",
"toast.model.none.title": "Nie wybrano modelu",
"toast.model.none.description": "Połącz dostawcę, aby podsumować tę sesję",
"toast.file.loadFailed.title": "Nie udało się załadować pliku",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index a7d328924a..cbb916a594 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "Переключиться к следующему уровню усилий",
"command.prompt.mode.shell": "Оболочка",
"command.prompt.mode.normal": "Промпт",
- "command.permissions.autoaccept.enable": "Авто-принятие изменений",
- "command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений",
+ "command.permissions.autoaccept.enable": "Автоматически принимать разрешения",
+ "command.permissions.autoaccept.disable": "Остановить автоматическое принятие разрешений",
"command.workspace.toggle": "Переключить рабочие пространства",
"command.workspace.toggle.description": "Включить или отключить несколько рабочих пространств в боковой панели",
"command.session.undo": "Отменить",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "Другие",
"dialog.provider.tag.recommended": "Рекомендуемые",
"dialog.provider.opencode.note": "Отобранные модели, включая Claude, GPT, Gemini и другие",
+ "dialog.provider.opencode.tagline": "Надежные оптимизированные модели",
+ "dialog.provider.opencodeGo.tagline": "Доступная подписка для всех",
"dialog.provider.anthropic.note": "Прямой доступ к моделям Claude, включая Pro и Max",
"dialog.provider.copilot.note": "ИИ-модели для помощи в кодировании через GitHub Copilot",
"dialog.provider.openai.note": "Модели GPT для быстрых и мощных задач общего ИИ",
@@ -398,10 +400,10 @@ export const dict = {
"toast.theme.title": "Тема переключена",
"toast.scheme.title": "Цветовая схема",
- "toast.permissions.autoaccept.on.title": "Авто-принятие изменений",
- "toast.permissions.autoaccept.on.description": "Разрешения на редактирование и запись будут автоматически одобрены",
- "toast.permissions.autoaccept.off.title": "Авто-принятие остановлено",
- "toast.permissions.autoaccept.off.description": "Редактирование и запись потребуют подтверждения",
+ "toast.permissions.autoaccept.on.title": "Разрешения принимаются автоматически",
+ "toast.permissions.autoaccept.on.description": "Запросы на разрешения будут одобряться автоматически",
+ "toast.permissions.autoaccept.off.title": "Автоматическое принятие разрешений остановлено",
+ "toast.permissions.autoaccept.off.description": "Запросы на разрешения будут требовать одобрения",
"toast.workspace.enabled.title": "Рабочие пространства включены",
"toast.workspace.enabled.description": "В боковой панели теперь отображаются несколько рабочих деревьев",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 1e9773bf02..c6a33dc676 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -71,8 +71,8 @@ export const dict = {
"command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป",
"command.prompt.mode.shell": "เชลล์",
"command.prompt.mode.normal": "พรอมต์",
- "command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ",
- "command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
+ "command.permissions.autoaccept.enable": "ยอมรับสิทธิ์โดยอัตโนมัติ",
+ "command.permissions.autoaccept.disable": "หยุดยอมรับสิทธิ์โดยอัตโนมัติ",
"command.workspace.toggle": "สลับพื้นที่ทำงาน",
"command.workspace.toggle.description": "เปิดหรือปิดใช้งานพื้นที่ทำงานหลายรายการในแถบด้านข้าง",
"command.session.undo": "ยกเลิก",
@@ -99,6 +99,8 @@ export const dict = {
"dialog.provider.group.other": "อื่น ๆ",
"dialog.provider.tag.recommended": "แนะนำ",
"dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ",
+ "dialog.provider.opencode.tagline": "โมเดลที่เชื่อถือได้และปรับให้เหมาะสม",
+ "dialog.provider.opencodeGo.tagline": "การสมัครสมาชิกราคาประหยัดสำหรับทุกคน",
"dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max",
"dialog.provider.copilot.note": "โมเดล AI สำหรับการช่วยเหลือในการเขียนโค้ดผ่าน GitHub Copilot",
"dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ",
@@ -401,10 +403,10 @@ export const dict = {
"toast.workspace.disabled.title": "ปิดใช้งานพื้นที่ทำงานแล้ว",
"toast.workspace.disabled.description": "จะแสดงเฉพาะ worktree หลักในแถบด้านข้าง",
- "toast.permissions.autoaccept.on.title": "กำลังยอมรับการแก้ไขโดยอัตโนมัติ",
- "toast.permissions.autoaccept.on.description": "สิทธิ์การแก้ไขและจะได้รับเขียนการอนุมัติโดยอัตโนมัติ",
- "toast.permissions.autoaccept.off.title": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
- "toast.permissions.autoaccept.off.description": "สิทธิ์การแก้ไขและเขียนจะต้องได้รับการอนุมัติ",
+ "toast.permissions.autoaccept.on.title": "กำลังยอมรับสิทธิ์โดยอัตโนมัติ",
+ "toast.permissions.autoaccept.on.description": "คำขอสิทธิ์จะได้รับการอนุมัติโดยอัตโนมัติ",
+ "toast.permissions.autoaccept.off.title": "หยุดยอมรับสิทธิ์โดยอัตโนมัติแล้ว",
+ "toast.permissions.autoaccept.off.description": "คำขอสิทธิ์จะต้องได้รับการอนุมัติ",
"toast.model.none.title": "ไม่ได้เลือกโมเดล",
"toast.model.none.description": "เชื่อมต่อผู้ให้บริการเพื่อสรุปเซสชันนี้",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index 62c7bb9ff2..0b57390836 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -96,8 +96,8 @@ export const dict = {
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "自动接受编辑",
- "command.permissions.autoaccept.disable": "停止自动接受编辑",
+ "command.permissions.autoaccept.enable": "自动接受权限",
+ "command.permissions.autoaccept.disable": "停止自动接受权限",
"command.workspace.toggle": "切换工作区",
"command.workspace.toggle.description": "在侧边栏启用或禁用多个工作区",
@@ -126,6 +126,8 @@ export const dict = {
"dialog.provider.group.other": "其他",
"dialog.provider.tag.recommended": "推荐",
"dialog.provider.opencode.note": "使用 OpenCode Zen 或 API 密钥连接",
+ "dialog.provider.opencode.tagline": "可靠的优化模型",
+ "dialog.provider.opencodeGo.tagline": "适合所有人的低成本订阅",
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接",
"dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接",
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接",
@@ -413,10 +415,10 @@ export const dict = {
"toast.workspace.enabled.description": "侧边栏现在显示多个工作树",
"toast.workspace.disabled.title": "工作区已禁用",
"toast.workspace.disabled.description": "侧边栏只显示主工作树",
- "toast.permissions.autoaccept.on.title": "自动接受编辑",
- "toast.permissions.autoaccept.on.description": "编辑和写入权限将自动获批",
- "toast.permissions.autoaccept.off.title": "已停止自动接受编辑",
- "toast.permissions.autoaccept.off.description": "编辑和写入权限将需要手动批准",
+ "toast.permissions.autoaccept.on.title": "正在自动接受权限",
+ "toast.permissions.autoaccept.on.description": "权限请求将被自动批准",
+ "toast.permissions.autoaccept.off.title": "已停止自动接受权限",
+ "toast.permissions.autoaccept.off.description": "权限请求将需要批准",
"toast.model.none.title": "未选择模型",
"toast.model.none.description": "请先连接提供商以总结此会话",
"toast.file.loadFailed.title": "加载文件失败",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index cb8f068f63..4e4509e201 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -75,8 +75,8 @@ export const dict = {
"command.model.variant.cycle.description": "切換到下一個強度等級",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
- "command.permissions.autoaccept.enable": "自動接受編輯",
- "command.permissions.autoaccept.disable": "停止自動接受編輯",
+ "command.permissions.autoaccept.enable": "自動接受權限",
+ "command.permissions.autoaccept.disable": "停止自動接受權限",
"command.workspace.toggle": "切換工作區",
"command.workspace.toggle.description": "在側邊欄啟用或停用多個工作區",
"command.session.undo": "復原",
@@ -103,6 +103,8 @@ export const dict = {
"dialog.provider.group.other": "其他",
"dialog.provider.tag.recommended": "推薦",
"dialog.provider.opencode.note": "精選模型,包含 Claude、GPT、Gemini 等等",
+ "dialog.provider.opencode.tagline": "可靠的優化模型",
+ "dialog.provider.opencodeGo.tagline": "適合所有人的低成本訂閱",
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 金鑰連線",
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 金鑰連線",
"dialog.provider.copilot.note": "使用 Copilot 或 API 金鑰連線",
@@ -400,10 +402,10 @@ export const dict = {
"toast.workspace.disabled.title": "工作區已停用",
"toast.workspace.disabled.description": "側邊欄只顯示主工作樹",
- "toast.permissions.autoaccept.on.title": "自動接受編輯",
- "toast.permissions.autoaccept.on.description": "編輯和寫入權限將自動獲准",
- "toast.permissions.autoaccept.off.title": "已停止自動接受編輯",
- "toast.permissions.autoaccept.off.description": "編輯和寫入權限將需要手動批准",
+ "toast.permissions.autoaccept.on.title": "正在自動接受權限",
+ "toast.permissions.autoaccept.on.description": "權限請求將被自動批准",
+ "toast.permissions.autoaccept.off.title": "已停止自動接受權限",
+ "toast.permissions.autoaccept.off.description": "權限請求將需要批准",
"toast.model.none.title": "未選擇模型",
"toast.model.none.description": "請先連線提供者以總結此工作階段",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 62094a6e42..cb194052d1 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -61,6 +61,7 @@ import {
displayName,
errorMessage,
getDraggableId,
+ latestRootSession,
sortedRootSessions,
syncWorkspaceOrder,
workspaceKey,
@@ -1093,14 +1094,51 @@ export default function Layout(props: ParentProps) {
return meta?.worktree ?? directory
}
- function navigateToProject(directory: string | undefined) {
+ async function navigateToProject(directory: string | undefined) {
if (!directory) return
const root = projectRoot(directory)
server.projects.touch(root)
+ const project = layout.projects.list().find((item) => item.worktree === root)
+ const dirs = Array.from(new Set([root, ...(store.workspaceOrder[root] ?? []), ...(project?.sandboxes ?? [])]))
+ const openSession = async (target: { directory: string; id: string }) => {
+ const resolved = await globalSDK.client.session
+ .get({ sessionID: target.id })
+ .then((x) => x.data)
+ .catch(() => undefined)
+ const next = resolved?.directory ? resolved : target
+ setStore("lastProjectSession", root, { directory: next.directory, id: next.id, at: Date.now() })
+ navigateWithSidebarReset(`/${base64Encode(next.directory)}/session/${next.id}`)
+ }
const projectSession = store.lastProjectSession[root]
if (projectSession?.id) {
- navigateWithSidebarReset(`/${base64Encode(projectSession.directory)}/session/${projectSession.id}`)
+ await openSession(projectSession)
+ return
+ }
+
+ const latest = latestRootSession(
+ dirs.map((item) => globalSync.child(item, { bootstrap: false })[0]),
+ Date.now(),
+ )
+ if (latest) {
+ await openSession(latest)
+ return
+ }
+
+ const fetched = latestRootSession(
+ await Promise.all(
+ dirs.map(async (item) => ({
+ path: { directory: item },
+ session: await globalSDK.client.session
+ .list({ directory: item })
+ .then((x) => x.data ?? [])
+ .catch(() => []),
+ })),
+ ),
+ Date.now(),
+ )
+ if (fetched) {
+ await openSession(fetched)
return
}
diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts
index 83d8f4748a..7627d9ba17 100644
--- a/packages/app/src/pages/layout/helpers.test.ts
+++ b/packages/app/src/pages/layout/helpers.test.ts
@@ -1,6 +1,25 @@
import { describe, expect, test } from "bun:test"
+import { type Session } from "@opencode-ai/sdk/v2/client"
import { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links"
-import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers"
+import {
+ displayName,
+ errorMessage,
+ getDraggableId,
+ latestRootSession,
+ syncWorkspaceOrder,
+ workspaceKey,
+} from "./helpers"
+
+const session = (input: Partial & Pick) =>
+ ({
+ title: "",
+ version: "v2",
+ parentID: undefined,
+ messageCount: 0,
+ permissions: { session: {}, share: {} },
+ time: { created: 0, updated: 0, archived: undefined },
+ ...input,
+ }) as Session
describe("layout deep links", () => {
test("parses open-project deep links", () => {
@@ -73,6 +92,61 @@ describe("layout workspace helpers", () => {
expect(result).toEqual(["/root", "/c", "/b"])
})
+ test("finds the latest root session across workspaces", () => {
+ const result = latestRootSession(
+ [
+ {
+ path: { directory: "/root" },
+ session: [session({ id: "root", directory: "/root", time: { created: 1, updated: 1, archived: undefined } })],
+ },
+ {
+ path: { directory: "/workspace" },
+ session: [
+ session({
+ id: "workspace",
+ directory: "/workspace",
+ time: { created: 2, updated: 2, archived: undefined },
+ }),
+ ],
+ },
+ ],
+ 120_000,
+ )
+
+ expect(result?.id).toBe("workspace")
+ })
+
+ test("ignores archived and child sessions when finding latest root session", () => {
+ const result = latestRootSession(
+ [
+ {
+ path: { directory: "/workspace" },
+ session: [
+ session({
+ id: "archived",
+ directory: "/workspace",
+ time: { created: 10, updated: 10, archived: 10 },
+ }),
+ session({
+ id: "child",
+ directory: "/workspace",
+ parentID: "parent",
+ time: { created: 20, updated: 20, archived: undefined },
+ }),
+ session({
+ id: "root",
+ directory: "/workspace",
+ time: { created: 30, updated: 30, archived: undefined },
+ }),
+ ],
+ },
+ ],
+ 120_000,
+ )
+
+ expect(result?.id).toBe("root")
+ })
+
test("extracts draggable id safely", () => {
expect(getDraggableId({ draggable: { id: "x" } })).toBe("x")
expect(getDraggableId({ draggable: { id: 42 } })).toBeUndefined()
diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts
index 6a1e7c0123..be4297fbe9 100644
--- a/packages/app/src/pages/layout/helpers.ts
+++ b/packages/app/src/pages/layout/helpers.ts
@@ -28,6 +28,11 @@ export const isRootVisibleSession = (session: Session, directory: string) =>
export const sortedRootSessions = (store: { session: Session[]; path: { directory: string } }, now: number) =>
store.session.filter((session) => isRootVisibleSession(session, store.path.directory)).sort(sortSessions(now))
+export const latestRootSession = (stores: { session: Session[]; path: { directory: string } }[], now: number) =>
+ stores
+ .flatMap((store) => store.session.filter((session) => isRootVisibleSession(session, store.path.directory)))
+ .sort(sortSessions(now))[0]
+
export const childMapByParent = (sessions: Session[]) => {
const map = new Map()
for (const session of sessions) {
diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx
index e19e6f430f..3c3652e38f 100644
--- a/packages/app/src/pages/layout/sidebar-project.tsx
+++ b/packages/app/src/pages/layout/sidebar-project.tsx
@@ -1,4 +1,5 @@
-import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
+import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"
+import { createStore } from "solid-js/store"
import { base64Encode } from "@opencode-ai/util/encode"
import { Button } from "@opencode-ai/ui/button"
import { ContextMenu } from "@opencode-ai/ui/context-menu"
@@ -7,7 +8,7 @@ import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { createSortable } from "@thisbeyond/solid-dnd"
-import { type LocalProject } from "@/context/layout"
+import { useLayout, type LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
import { useNotification } from "@/context/notification"
@@ -60,6 +61,7 @@ const ProjectTile = (props: {
selected: Accessor
active: Accessor
overlay: Accessor
+ suppressHover: Accessor
dirs: Accessor
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
onProjectMouseLeave: (worktree: string) => void
@@ -71,9 +73,11 @@ const ProjectTile = (props: {
closeProject: (directory: string) => void
setMenu: (value: boolean) => void
setOpen: (value: boolean) => void
+ setSuppressHover: (value: boolean) => void
language: ReturnType
}): JSX.Element => {
const notification = useNotification()
+ const layout = useLayout()
const unseenCount = createMemo(() =>
props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
)
@@ -107,17 +111,28 @@ const ProjectTile = (props: {
}}
onMouseEnter={(event: MouseEvent) => {
if (!props.overlay()) return
+ if (props.suppressHover()) return
props.onProjectMouseEnter(props.project.worktree, event)
}}
onMouseLeave={() => {
+ if (props.suppressHover()) props.setSuppressHover(false)
if (!props.overlay()) return
props.onProjectMouseLeave(props.project.worktree)
}}
onFocus={() => {
if (!props.overlay()) return
+ if (props.suppressHover()) return
props.onProjectFocus(props.project.worktree)
}}
- onClick={() => props.navigateToProject(props.project.worktree)}
+ onClick={() => {
+ if (props.selected()) {
+ props.setSuppressHover(true)
+ layout.sidebar.toggle()
+ return
+ }
+ props.setSuppressHover(false)
+ props.navigateToProject(props.project.worktree)
+ }}
onBlur={() => props.setOpen(false)}
>
@@ -278,16 +293,19 @@ export const SortableProject = (props: {
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
- const [open, setOpen] = createSignal(false)
- const [menu, setMenu] = createSignal(false)
+ const [state, setState] = createStore({
+ open: false,
+ menu: false,
+ suppressHover: false,
+ })
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
const active = createMemo(() =>
projectTileActive({
- menu: menu(),
+ menu: state.menu,
preview: preview(),
- open: open(),
+ open: state.open,
overlay: overlay(),
hoverProject: props.ctx.hoverProject(),
worktree: props.project.worktree,
@@ -296,8 +314,14 @@ export const SortableProject = (props: {
createEffect(() => {
if (preview()) return
- if (!open()) return
- setOpen(false)
+ if (!state.open) return
+ setState("open", false)
+ })
+
+ createEffect(() => {
+ if (!selected()) return
+ if (!state.open) return
+ setState("open", false)
})
const label = (directory: string) => {
@@ -328,6 +352,7 @@ export const SortableProject = (props: {
selected={selected}
active={active}
overlay={overlay}
+ suppressHover={() => state.suppressHover}
dirs={dirs}
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
@@ -337,8 +362,9 @@ export const SortableProject = (props: {
toggleProjectWorkspaces={props.ctx.toggleProjectWorkspaces}
workspacesEnabled={props.ctx.workspacesEnabled}
closeProject={props.ctx.closeProject}
- setMenu={setMenu}
- setOpen={setOpen}
+ setMenu={(value) => setState("menu", value)}
+ setOpen={(value) => setState("open", value)}
+ setSuppressHover={(value) => setState("suppressHover", value)}
language={language}
/>
)
@@ -346,17 +372,18 @@ export const SortableProject = (props: {
return (
// @ts-ignore
-
+
{
- if (menu()) return
- setOpen(value)
+ if (state.menu) return
+ if (value && state.suppressHover) return
+ setState("open", value)
if (value) props.ctx.setHoverSession(undefined)
}}
>
@@ -371,7 +398,7 @@ export const SortableProject = (props: {
projectChildren={projectChildren}
workspaceSessions={workspaceSessions}
workspaceChildren={workspaceChildren}
- setOpen={setOpen}
+ setOpen={(value) => setState("open", value)}
ctx={props.ctx}
language={language}
/>
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 2e440a6b03..6751f4186f 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -416,7 +416,7 @@ export default function Page() {
)
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
- const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened())
+ const reviewTab = createMemo(() => isDesktop())
const fileTreeTab = () => layout.fileTree.tab()
const setFileTreeTab = (value: "changes" | "all") => layout.fileTree.setTab(value)
@@ -700,33 +700,12 @@ export default function Page() {
const active = tabs().active()
const tab = active === "review" || (!active && hasReview()) ? "changes" : "all"
layout.fileTree.setTab(tab)
- return
}
-
- if (fileTreeTab() !== "changes") return
- tabs().setActive("review")
},
{ defer: true },
),
)
- createEffect(() => {
- if (!isDesktop()) return
- if (!layout.fileTree.opened()) return
- if (fileTreeTab() !== "all") return
-
- const active = tabs().active()
- if (active && active !== "review") return
-
- const first = openedTabs()[0]
- if (first) {
- tabs().setActive(first)
- return
- }
-
- if (contextOpen()) tabs().setActive("context")
- })
-
createEffect(() => {
const id = params.id
if (!id) return
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx
index 9349e99376..fd2f3b2bd8 100644
--- a/packages/app/src/pages/session/review-tab.tsx
+++ b/packages/app/src/pages/session/review-tab.tsx
@@ -1,5 +1,4 @@
import { createEffect, on, onCleanup, type JSX } from "solid-js"
-import { createStore } from "solid-js/store"
import type { FileDiff } from "@opencode-ai/sdk/v2"
import { SessionReview } from "@opencode-ai/ui/session-review"
import type { SelectedLineRange } from "@/context/file"
@@ -31,38 +30,8 @@ export interface SessionReviewTabProps {
}
export function StickyAddButton(props: { children: JSX.Element }) {
- const [state, setState] = createStore({ stuck: false })
- let button: HTMLDivElement | undefined
-
- createEffect(() => {
- const node = button
- if (!node) return
-
- const scroll = node.parentElement
- if (!scroll) return
-
- const handler = () => {
- const rect = node.getBoundingClientRect()
- const scrollRect = scroll.getBoundingClientRect()
- setState("stuck", rect.right >= scrollRect.right && scroll.scrollWidth > scroll.clientWidth)
- }
-
- scroll.addEventListener("scroll", handler, { passive: true })
- const observer = new ResizeObserver(handler)
- observer.observe(scroll)
- handler()
- onCleanup(() => {
- scroll.removeEventListener("scroll", handler)
- observer.disconnect()
- })
- })
-
return (
-
+
{props.children}
)
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index 07b18f3146..5c8efff381 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -47,7 +47,7 @@ export function SessionSidePanel(props: {
const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
const open = createMemo(() => isDesktop() && (view().reviewPanel.opened() || layout.fileTree.opened()))
- const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened())
+ const reviewTab = createMemo(() => isDesktop())
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
@@ -202,133 +202,123 @@ export function SessionSidePanel(props: {
>
-
-
-
-
-
-
{
- const stop = createFileTabListSync({ el, contextOpen })
- onCleanup(stop)
- }}
- >
-
-
-
-
{language.t("session.tab.review")}
-
-
- {reviewCount()}
-
-
-
-
-
-
-
- tabs().close("context")}
- aria-label={language.t("common.closeTab")}
- />
-
- }
- hideCloseButton
- onMiddleClick={() => tabs().close("context")}
- >
-
-
-
{language.t("session.tab.context")}
-
-
-
-
- {(tab) => }
-
-
-
-
- dialog.show(() => )
- }
- aria-label={language.t("command.file.open")}
- />
-
-
-
-
-
-
-
- {props.reviewPanel()}
-
-
-
-
-
-
-
-
-
- {language.t("session.files.selectToOpen")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {(tab) => }
-
-
-
-
- {(tab) => {
- const path = createMemo(() => file.pathFromTab(tab))
- return (
-
- {(p) => }
-
- )
- }}
-
-
-
- }
+
- {props.reviewPanel()}
-
+
+
+
+
+
{
+ const stop = createFileTabListSync({ el, contextOpen })
+ onCleanup(stop)
+ }}
+ >
+
+
+
+
{language.t("session.tab.review")}
+
+ {reviewCount()}
+
+
+
+
+
+
+ tabs().close("context")}
+ aria-label={language.t("common.closeTab")}
+ />
+
+ }
+ hideCloseButton
+ onMiddleClick={() => tabs().close("context")}
+ >
+
+
+
{language.t("session.tab.context")}
+
+
+
+
+ {(tab) => }
+
+
+
+ dialog.show(() => )}
+ aria-label={language.t("command.file.open")}
+ />
+
+
+
+
+
+
+
+ {props.reviewPanel()}
+
+
+
+
+
+
+
+
+
+ {language.t("session.files.selectToOpen")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {(tab) => }
+
+
+
+
+ {(tab) => {
+ const path = createMemo(() => file.pathFromTab(tab))
+ return (
+
+ {(p) => }
+
+ )
+ }}
+
+
+
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index 05d2309a42..ad5813ced6 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.2.14",
+ "version": "1.2.15",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index 1719625839..a6aee5368e 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -92,6 +92,7 @@ export async function handler(
const stickyProvider = await stickyTracker?.get()
const authInfo = await authenticate(modelInfo)
const billingSource = validateBilling(authInfo, modelInfo)
+ logger.metric({ source: billingSource })
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
const providerInfo = selectProvider(
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 078d662072..8f65e0c457 100644
--- a/packages/console/core/package.json
+++ b/packages/console/core/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
- "version": "1.2.14",
+ "version": "1.2.15",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index ac1c6bfd89..6cdf752432 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.2.14",
+ "version": "1.2.15",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/console/function/src/log-processor.ts b/packages/console/function/src/log-processor.ts
index 327fc930b7..f8b2cf5270 100644
--- a/packages/console/function/src/log-processor.ts
+++ b/packages/console/function/src/log-processor.ts
@@ -13,7 +13,11 @@ export default {
url.pathname !== "/zen/v1/chat/completions" &&
url.pathname !== "/zen/v1/messages" &&
url.pathname !== "/zen/v1/responses" &&
- !url.pathname.startsWith("/zen/v1/models/")
+ !url.pathname.startsWith("/zen/v1/models/") &&
+ url.pathname !== "/zen/go/v1/chat/completions" &&
+ url.pathname !== "/zen/go/v1/messages" &&
+ url.pathname !== "/zen/go/v1/responses" &&
+ !url.pathname.startsWith("/zen/go/v1/models/")
)
return
diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json
index 1b91c7cbe0..09344f7fa2 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.2.14",
+ "version": "1.2.15",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 2bd9cce9a3..4fe999e700 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "1.2.14",
+ "version": "1.2.15",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/desktop/scripts/utils.ts b/packages/desktop/scripts/utils.ts
index f6ea7009c8..2629eb466c 100644
--- a/packages/desktop/scripts/utils.ts
+++ b/packages/desktop/scripts/utils.ts
@@ -8,7 +8,7 @@ export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; ass
},
{
rustTarget: "x86_64-apple-darwin",
- ocBinary: "opencode-darwin-x64",
+ ocBinary: "opencode-darwin-x64-baseline",
assetExt: "zip",
},
{
diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs
index af1a45cf3a..97fdba144f 100644
--- a/packages/desktop/src-tauri/src/cli.rs
+++ b/packages/desktop/src-tauri/src/cli.rs
@@ -4,10 +4,13 @@ use process_wrap::tokio::CommandWrap;
use process_wrap::tokio::ProcessGroup;
#[cfg(windows)]
use process_wrap::tokio::{CommandWrapper, JobObject, KillOnDrop};
+use std::collections::HashMap;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
+use std::path::Path;
+use std::process::Stdio;
use std::sync::Arc;
-use std::{process::Stdio, time::Duration};
+use std::time::{Duration, Instant};
use tauri::{AppHandle, Manager, path::BaseDirectory};
use tauri_specta::Event;
use tokio::{
@@ -39,6 +42,7 @@ impl CommandWrapper for WinCreationFlags {
const CLI_INSTALL_DIR: &str = ".opencode/bin";
const CLI_BINARY_NAME: &str = "opencode";
+const SHELL_ENV_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(serde::Deserialize, Debug)]
pub struct ServerConfig {
@@ -232,6 +236,133 @@ fn shell_escape(input: &str) -> String {
escaped
}
+fn parse_shell_env(stdout: &[u8]) -> HashMap
{
+ String::from_utf8_lossy(stdout)
+ .split('\0')
+ .filter_map(|line| {
+ if line.is_empty() {
+ return None;
+ }
+
+ let (key, value) = line.split_once('=')?;
+ if key.is_empty() {
+ return None;
+ }
+
+ Some((key.to_string(), value.to_string()))
+ })
+ .collect()
+}
+
+fn command_output_with_timeout(
+ mut cmd: std::process::Command,
+ timeout: Duration,
+) -> std::io::Result }>
+ {(preloaded) => (
+
+
+
+ )}
+
+
+ )
+ },
+}
+
+export const Split = {
+ render: () => {
+ const [data] = createResource(loadSplit)
+ return (
+
+ Loading pre-rendered diff... }>
+ {(preloaded) => (
+
+
+
+ )}
+
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/diff.stories.tsx b/packages/ui/src/components/diff.stories.tsx
new file mode 100644
index 0000000000..03bf4a0e0f
--- /dev/null
+++ b/packages/ui/src/components/diff.stories.tsx
@@ -0,0 +1,96 @@
+// @ts-nocheck
+import * as mod from "./diff"
+import { create } from "../storybook/scaffold"
+import { diff } from "../storybook/fixtures"
+
+const docs = `### Overview
+Render a code diff with OpenCode styling using the Pierre diff engine.
+
+Pair with \`DiffChanges\` for summary counts.
+Use \`LineComment\` or external UI for annotation workflows.
+
+### API
+- Required: \`before\` and \`after\` file contents (name + contents).
+- Optional: \`diffStyle\` ("unified" | "split"), \`annotations\`, \`selectedLines\`, \`commentedLines\`.
+- Optional interaction: \`enableLineSelection\`, \`onLineSelectionEnd\`.
+- Passes through Pierre FileDiff options (see component source).
+
+### Variants and states
+- Unified and split diff styles.
+- Optional line selection + commented line highlighting.
+
+### Behavior
+- Re-renders when \`before\`/\`after\` or diff options change.
+- Line selection uses mouse drag/selection when enabled.
+
+### Accessibility
+- TODO: confirm keyboard behavior from the Pierre diff engine.
+- Provide surrounding labels or headings when used as a standalone view.
+
+### Theming/tokens
+- Uses \`data-component="diff"\` and Pierre CSS variables from \`styleVariables\`.
+- Colors derive from theme tokens (diff add/delete, background, text).
+
+`
+
+const story = create({
+ title: "UI/Diff",
+ mod,
+ args: {
+ before: diff.before,
+ after: diff.after,
+ diffStyle: "unified",
+ },
+})
+
+export default {
+ title: "UI/Diff",
+ id: "components-diff",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ diffStyle: {
+ control: "select",
+ options: ["unified", "split"],
+ },
+ enableLineSelection: {
+ control: "boolean",
+ },
+ },
+}
+
+export const Unified = story.Basic
+
+export const Split = {
+ args: {
+ diffStyle: "split",
+ },
+}
+
+export const Selectable = {
+ args: {
+ enableLineSelection: true,
+ },
+}
+
+export const SelectedLines = {
+ args: {
+ selectedLines: { start: 2, end: 4 },
+ },
+}
+
+export const CommentedLines = {
+ args: {
+ commentedLines: [
+ { start: 1, end: 1 },
+ { start: 4, end: 4 },
+ ],
+ },
+}
diff --git a/packages/ui/src/components/dock-prompt.stories.tsx b/packages/ui/src/components/dock-prompt.stories.tsx
new file mode 100644
index 0000000000..de017a1ba2
--- /dev/null
+++ b/packages/ui/src/components/dock-prompt.stories.tsx
@@ -0,0 +1,62 @@
+// @ts-nocheck
+import * as mod from "./dock-prompt"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Docked prompt layout for questions and permission requests.
+
+Use with form controls or confirmation buttons in the footer.
+
+### API
+- Required: \`kind\` (question | permission), \`header\`, \`children\`, \`footer\`.
+- Optional: \`ref\` for measuring or focus management.
+
+### Variants and states
+- Question and permission layouts (data attributes).
+
+### Behavior
+- Pure layout component; behavior handled by parent.
+
+### Accessibility
+- Ensure header and footer content provide clear context and actions.
+
+### Theming/tokens
+- Uses \`data-component="dock-prompt"\` with kind data attribute.
+
+`
+
+const story = create({
+ title: "UI/DockPrompt",
+ mod,
+ args: {
+ kind: "question",
+ header: "Header",
+ children: "Prompt content",
+ footer: "Footer",
+ },
+})
+
+export default {
+ title: "UI/DockPrompt",
+ id: "components-dock-prompt",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Permission = {
+ args: {
+ kind: "permission",
+ header: "Allow access?",
+ children: "This action needs permission to proceed.",
+ footer: "Approve or deny",
+ },
+}
diff --git a/packages/ui/src/components/dropdown-menu.stories.tsx b/packages/ui/src/components/dropdown-menu.stories.tsx
new file mode 100644
index 0000000000..7a2d644fac
--- /dev/null
+++ b/packages/ui/src/components/dropdown-menu.stories.tsx
@@ -0,0 +1,97 @@
+// @ts-nocheck
+import * as mod from "./dropdown-menu"
+import { Button } from "./button"
+
+const docs = `### Overview
+Dropdown menu built on Kobalte with composable items, groups, and submenus.
+
+Use \`DropdownMenu.ItemLabel\`/\`ItemDescription\` for richer rows.
+
+### API
+- Root accepts Kobalte DropdownMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
+- Compose with \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections.
+
+### Variants and states
+- Supports item groups, separators, and nested submenus.
+
+### Behavior
+- Menu opens from trigger and renders in a portal by default.
+
+### Accessibility
+- TODO: confirm keyboard navigation from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="dropdown-menu"\` and slot attributes for styling.
+
+`
+
+export default {
+ title: "UI/DropdownMenu",
+ id: "components-dropdown-menu",
+ component: mod.DropdownMenu,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+
+ Open menu
+
+
+
+
+ Actions
+
+ New file
+
+
+ Rename
+ Shift+R
+
+
+
+
+ More options
+
+
+ Duplicate
+
+
+ Move
+
+
+
+
+
+
+ ),
+}
+
+export const CheckboxRadio = {
+ render: () => (
+
+
+ Open menu
+
+
+
+ Show line numbers
+ Wrap lines
+
+
+ Compact
+ Comfortable
+
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/favicon.stories.tsx b/packages/ui/src/components/favicon.stories.tsx
new file mode 100644
index 0000000000..a693c0f460
--- /dev/null
+++ b/packages/ui/src/components/favicon.stories.tsx
@@ -0,0 +1,49 @@
+// @ts-nocheck
+import * as mod from "./favicon"
+
+const docs = `### Overview
+Injects favicon and app icon meta tags for the document head.
+
+Render once near the app root (head management).
+
+### API
+- No props.
+
+### Variants and states
+- Single configuration.
+
+### Behavior
+- Registers link and meta tags via Solid Meta components.
+
+### Accessibility
+- Not applicable.
+
+### Theming/tokens
+- Not applicable.
+
+`
+
+export default {
+ title: "UI/Favicon",
+ id: "components-favicon",
+ component: mod.Favicon,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+
+
+ Head tags are injected for favicon and app icons.
+
+
+ ),
+}
diff --git a/packages/ui/src/components/file-icon.stories.tsx b/packages/ui/src/components/file-icon.stories.tsx
new file mode 100644
index 0000000000..937328502f
--- /dev/null
+++ b/packages/ui/src/components/file-icon.stories.tsx
@@ -0,0 +1,94 @@
+// @ts-nocheck
+import * as mod from "./file-icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+File and folder icon renderer based on file name and extension.
+
+Use in file trees and lists.
+
+### API
+- Required: \`node\` with \`path\` and \`type\`.
+- Optional: \`expanded\` (for folders), \`mono\` for monochrome rendering.
+
+### Variants and states
+- Folder vs file icons; expanded folder variant.
+
+### Behavior
+- Maps file names and extensions to sprite icons.
+
+### Accessibility
+- Provide adjacent text labels for filenames; icons are decorative.
+
+### Theming/tokens
+- Uses \`data-component="file-icon"\` and sprite-based styling.
+
+`
+
+const story = create({
+ title: "UI/FileIcon",
+ mod,
+ args: {
+ node: { path: "package.json", type: "file" },
+ mono: true,
+ },
+})
+
+export default {
+ title: "UI/FileIcon",
+ id: "components-file-icon",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Folder = {
+ args: {
+ node: { path: "src", type: "directory" },
+ expanded: true,
+ mono: false,
+ },
+}
+
+export const Samples = {
+ render: () => {
+ const items = [
+ { path: "README.md", type: "file" },
+ { path: "package.json", type: "file" },
+ { path: "tsconfig.json", type: "file" },
+ { path: "index.ts", type: "file" },
+ { path: "styles.css", type: "file" },
+ { path: "logo.svg", type: "file" },
+ { path: "photo.png", type: "file" },
+ { path: "Dockerfile", type: "file" },
+ { path: ".env", type: "file" },
+ { path: "src", type: "directory" },
+ { path: "public", type: "directory" },
+ ] as const
+
+ return (
+
+ {items.map((node) => (
+
+ ))}
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/font.stories.tsx b/packages/ui/src/components/font.stories.tsx
new file mode 100644
index 0000000000..153a2c8dc9
--- /dev/null
+++ b/packages/ui/src/components/font.stories.tsx
@@ -0,0 +1,48 @@
+// @ts-nocheck
+import * as mod from "./font"
+
+const docs = `### Overview
+Loads OpenCode typography assets and mono nerd fonts.
+
+Render once at the app root or Storybook preview.
+
+### API
+- No props.
+
+### Variants and states
+- Fonts include sans and multiple mono families.
+
+### Behavior
+- Injects @font-face rules and preload links into the document head.
+
+### Accessibility
+- Not applicable.
+
+### Theming/tokens
+- Provides font families used by theme tokens.
+
+`
+
+export default {
+ title: "UI/Font",
+ id: "components-font",
+ component: mod.Font,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+
+
OpenCode Sans Sample
+
OpenCode Mono Sample
+
+ ),
+}
diff --git a/packages/ui/src/components/hover-card.stories.tsx b/packages/ui/src/components/hover-card.stories.tsx
new file mode 100644
index 0000000000..3f5cf10281
--- /dev/null
+++ b/packages/ui/src/components/hover-card.stories.tsx
@@ -0,0 +1,70 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./hover-card"
+
+const docs = `### Overview
+Hover-triggered card for lightweight previews and metadata.
+
+Use for short summaries; avoid dense interactive controls.
+
+### API
+- Required: \`trigger\` element.
+- Children render inside the hover card body.
+
+### Variants and states
+- None; content and trigger are fully composable.
+
+### Behavior
+- Opens on hover/focus over the trigger.
+
+### Accessibility
+- TODO: confirm focus and hover intent behavior from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="hover-card-content"\` and slots for styling.
+
+`
+
+export default {
+ title: "UI/HoverCard",
+ id: "components-hover-card",
+ component: mod.HoverCard,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+ Hover me}>
+
+
Preview
+
Short supporting text.
+
+
+ ),
+}
+
+export const InlineMount = {
+ render: () => {
+ const [mount, setMount] = createSignal(undefined)
+ return (
+
+
Hover me}
+ >
+
+
Mounted inside
+
Uses custom mount node.
+
+
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/icon-button.stories.tsx b/packages/ui/src/components/icon-button.stories.tsx
new file mode 100644
index 0000000000..9782759f82
--- /dev/null
+++ b/packages/ui/src/components/icon-button.stories.tsx
@@ -0,0 +1,74 @@
+// @ts-nocheck
+import * as mod from "./icon-button"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Compact icon-only button with size and variant control.
+
+Use \`Button\` for text labels and primary actions.
+
+### API
+- Required: \`icon\` icon name.
+- Optional: \`size\`, \`iconSize\`, \`variant\`.
+- Inherits Kobalte Button props and native button attributes.
+
+### Variants and states
+- Variants: primary, secondary, ghost.
+- Sizes: small, normal, large.
+
+### Behavior
+- Icon size adapts to button size unless overridden.
+
+### Accessibility
+- Provide \`aria-label\` when there is no visible text.
+
+### Theming/tokens
+- Uses \`data-component="icon-button"\` and size/variant data attributes.
+
+`
+
+const story = create({ title: "UI/IconButton", mod, args: { icon: "check", "aria-label": "Icon" } })
+export default {
+ title: "UI/IconButton",
+ id: "components-icon-button",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const Variants = {
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const IconSizeOverride = {
+ render: () => (
+
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/icon.stories.tsx b/packages/ui/src/components/icon.stories.tsx
new file mode 100644
index 0000000000..1986d74772
--- /dev/null
+++ b/packages/ui/src/components/icon.stories.tsx
@@ -0,0 +1,170 @@
+// @ts-nocheck
+import * as mod from "./icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Inline icon renderer using the built-in OpenCode icon set.
+
+Use with \`Button\`, \`IconButton\`, and menu items.
+
+### API
+- Required: \`name\` (icon key).
+- Optional: \`size\` (small | normal | medium | large).
+- Accepts standard SVG props.
+
+### Variants and states
+- Size variants only.
+
+### Behavior
+- Uses an internal SVG path map.
+
+### Accessibility
+- Icons are aria-hidden by default; wrap with accessible text when needed.
+
+### Theming/tokens
+- Uses \`data-component="icon"\` with size data attributes.
+
+`
+
+const names = [
+ "align-right",
+ "arrow-up",
+ "arrow-left",
+ "arrow-right",
+ "archive",
+ "bubble-5",
+ "prompt",
+ "brain",
+ "bullet-list",
+ "check-small",
+ "chevron-down",
+ "chevron-left",
+ "chevron-right",
+ "chevron-grabber-vertical",
+ "chevron-double-right",
+ "circle-x",
+ "close",
+ "close-small",
+ "checklist",
+ "console",
+ "expand",
+ "collapse",
+ "code",
+ "code-lines",
+ "circle-ban-sign",
+ "edit-small-2",
+ "eye",
+ "enter",
+ "folder",
+ "file-tree",
+ "file-tree-active",
+ "magnifying-glass",
+ "plus-small",
+ "plus",
+ "new-session",
+ "pencil-line",
+ "mcp",
+ "glasses",
+ "magnifying-glass-menu",
+ "window-cursor",
+ "task",
+ "stop",
+ "layout-left",
+ "layout-left-partial",
+ "layout-left-full",
+ "layout-right",
+ "layout-right-partial",
+ "layout-right-full",
+ "square-arrow-top-right",
+ "open-file",
+ "speech-bubble",
+ "comment",
+ "folder-add-left",
+ "github",
+ "discord",
+ "layout-bottom",
+ "layout-bottom-partial",
+ "layout-bottom-full",
+ "dot-grid",
+ "circle-check",
+ "copy",
+ "check",
+ "photo",
+ "share",
+ "download",
+ "menu",
+ "server",
+ "branch",
+ "edit",
+ "help",
+ "settings-gear",
+ "dash",
+ "cloud-upload",
+ "trash",
+ "sliders",
+ "keyboard",
+ "selector",
+ "arrow-down-to-line",
+ "warning",
+ "link",
+ "providers",
+ "models",
+]
+
+const story = create({ title: "UI/Icon", mod, args: { name: "check" } })
+
+export default {
+ title: "UI/Icon",
+ id: "components-icon",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ name: {
+ control: "select",
+ options: names,
+ },
+ size: {
+ control: "select",
+ options: ["small", "normal", "medium", "large"],
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+ render: () => (
+
+
+
+
+
+
+ ),
+}
+
+export const Gallery = {
+ render: () => (
+
+ {names.map((name) => (
+
+ ))}
+
+ ),
+}
diff --git a/packages/ui/src/components/image-preview.stories.tsx b/packages/ui/src/components/image-preview.stories.tsx
new file mode 100644
index 0000000000..f0a00c7825
--- /dev/null
+++ b/packages/ui/src/components/image-preview.stories.tsx
@@ -0,0 +1,59 @@
+// @ts-nocheck
+import { onMount } from "solid-js"
+import * as mod from "./image-preview"
+import { Button } from "./button"
+import { useDialog } from "../context/dialog"
+
+const docs = `### Overview
+Image preview content intended to render inside the dialog stack.
+
+Use for full-size image inspection; keep images optimized.
+
+### API
+- Required: \`src\`.
+- Optional: \`alt\` text.
+
+### Variants and states
+- Single layout with close action.
+
+### Behavior
+- Intended to be used via \`useDialog().show\`.
+
+### Accessibility
+- Uses localized aria-label for close button.
+
+### Theming/tokens
+- Uses \`data-component="image-preview"\` and slot attributes.
+
+`
+
+export default {
+ title: "UI/ImagePreview",
+ id: "components-image-preview",
+ component: mod.ImagePreview,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => {
+ const dialog = useDialog()
+ const src = "https://placehold.co/640x360/png"
+
+ const open = () => dialog.show(() => )
+
+ onMount(open)
+
+ return (
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/inline-input.stories.tsx b/packages/ui/src/components/inline-input.stories.tsx
new file mode 100644
index 0000000000..e364c89631
--- /dev/null
+++ b/packages/ui/src/components/inline-input.stories.tsx
@@ -0,0 +1,50 @@
+// @ts-nocheck
+import * as mod from "./inline-input"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Compact inline input for short values.
+
+Use inside text or table rows for quick edits.
+
+### API
+- Optional: \`width\` to set a fixed width.
+- Accepts standard input props.
+
+### Variants and states
+- No built-in variants; style via class or width.
+
+### Behavior
+- Uses inline width when provided.
+
+### Accessibility
+- Provide a label or aria-label when used standalone.
+
+### Theming/tokens
+- Uses \`data-component="inline-input"\`.
+
+`
+
+const story = create({ title: "UI/InlineInput", mod, args: { placeholder: "Type...", value: "Inline" } })
+export default {
+ title: "UI/InlineInput",
+ id: "components-inline-input",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const FixedWidth = {
+ args: {
+ value: "80px",
+ width: "80px",
+ },
+}
diff --git a/packages/ui/src/components/keybind.stories.tsx b/packages/ui/src/components/keybind.stories.tsx
new file mode 100644
index 0000000000..a458a53a74
--- /dev/null
+++ b/packages/ui/src/components/keybind.stories.tsx
@@ -0,0 +1,43 @@
+// @ts-nocheck
+import * as mod from "./keybind"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Keyboard shortcut pill for displaying keybindings.
+
+Pair with menu items or command palettes.
+
+### API
+- Children render the key sequence text.
+- Accepts standard span props.
+
+### Variants and states
+- Single visual style.
+
+### Behavior
+- Presentational only.
+
+### Accessibility
+- Ensure text conveys the shortcut (e.g., "Cmd+K").
+
+### Theming/tokens
+- Uses \`data-component="keybind"\`.
+
+`
+
+const story = create({ title: "UI/Keybind", mod, args: { children: "Cmd+K" } })
+export default {
+ title: "UI/Keybind",
+ id: "components-keybind",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/line-comment.stories.tsx b/packages/ui/src/components/line-comment.stories.tsx
new file mode 100644
index 0000000000..c48674e3c7
--- /dev/null
+++ b/packages/ui/src/components/line-comment.stories.tsx
@@ -0,0 +1,115 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./line-comment"
+
+const docs = `### Overview
+Inline comment anchor and editor for code review or annotation flows.
+
+Pair with \`Diff\` or \`Code\` to align comments to lines.
+
+### API
+- \`LineCommentAnchor\`: position with \`top\`, control \`open\`, render custom children.
+- \`LineComment\`: convenience wrapper for displaying comment + selection label.
+- \`LineCommentEditor\`: controlled textarea with submit/cancel handlers.
+
+### Variants and states
+- Default display and editor display variants.
+
+### Behavior
+- Anchor positions relative to a containing element.
+- Editor submits on Enter (Shift+Enter for newline).
+
+### Accessibility
+- TODO: confirm ARIA labeling for comment button and editor textarea.
+
+### Theming/tokens
+- Uses \`data-component="line-comment"\` and related slots.
+
+`
+
+export default {
+ title: "UI/LineComment",
+ id: "components-line-comment",
+ component: mod.LineComment,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Default = {
+ render: () => (
+
+
12 | const total = sum(values)
+
13 | return total / values.length
+
+
+ ),
+}
+
+export const Editor = {
+ render: () => {
+ const [value, setValue] = createSignal("Add context for this change.")
+ return (
+
+
40 | if (values.length === 0) return 0
+
setValue("")}
+ onSubmit={(next) => setValue(next)}
+ />
+
+ )
+ },
+}
+
+export const AnchorOnly = {
+ render: () => (
+
+
20 | const ready = true
+
+ Anchor content
+
+
+ ),
+}
diff --git a/packages/ui/src/components/list.stories.tsx b/packages/ui/src/components/list.stories.tsx
new file mode 100644
index 0000000000..280f323c0b
--- /dev/null
+++ b/packages/ui/src/components/list.stories.tsx
@@ -0,0 +1,170 @@
+// @ts-nocheck
+import * as mod from "./list"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Filterable list with keyboard navigation and optional search input.
+
+Use within panels or popovers where keyboard navigation is expected.
+
+### API
+- Required: \`items\` and \`key\`.
+- Required: \`children\` render function for items.
+- Optional: \`search\`, \`filterKeys\`, \`groupBy\`, \`onSelect\`, \`onKeyEvent\`.
+
+### Variants and states
+- Optional search bar and group headers.
+
+### Behavior
+- Uses fuzzy search when \`search\` is enabled.
+- Keyboard navigation via arrow keys; Enter selects.
+
+### Accessibility
+- TODO: confirm ARIA roles for list items and search input.
+
+### Theming/tokens
+- Uses \`data-component="list"\` and data slots for structure.
+
+`
+
+const story = create({
+ title: "UI/List",
+ mod,
+ args: {
+ items: ["One", "Two", "Three", "Four"],
+ key: (x: string) => x,
+ children: (x: string) => x,
+ search: true,
+ },
+})
+
+export default {
+ title: "UI/List",
+ id: "components-list",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Grouped = {
+ render: () => {
+ const items = [
+ { id: "a1", title: "Alpha", group: "Group A" },
+ { id: "a2", title: "Bravo", group: "Group A" },
+ { id: "b1", title: "Delta", group: "Group B" },
+ ]
+ return (
+ item.id} groupBy={(item) => item.group} search={true}>
+ {(item) => item.title}
+
+ )
+ },
+}
+
+export const Empty = {
+ render: () => (
+ item} search={true}>
+ {(item) => item}
+
+ ),
+}
+
+export const WithAdd = {
+ render: () => (
+ item}
+ search={true}
+ add={{
+ render: () => (
+
+ ),
+ }}
+ >
+ {(item) => item}
+
+ ),
+}
+
+export const Divider = {
+ render: () => (
+ item} divider={true}>
+ {(item) => item}
+
+ ),
+}
+
+export const ActiveIcon = {
+ render: () => (
+ item} activeIcon="chevron-right">
+ {(item) => item}
+
+ ),
+}
+
+export const NoSearch = {
+ render: () => (
+ item} search={false}>
+ {(item) => item}
+
+ ),
+}
+
+export const SearchOptions = {
+ render: () => (
+ item}
+ search={{
+ placeholder: "Filter...",
+ hideIcon: true,
+ action: ,
+ }}
+ >
+ {(item) => item}
+
+ ),
+}
+
+export const ItemWrapper = {
+ render: () => (
+ item}
+ itemWrapper={(item, node) => (
+ {node}
+ )}
+ >
+ {(item) => item}
+
+ ),
+}
+
+export const GroupHeader = {
+ render: () => {
+ const items = [
+ { id: "a1", title: "Alpha", group: "Group A" },
+ { id: "b1", title: "Beta", group: "Group B" },
+ ]
+ return (
+ item.id}
+ groupBy={(item) => item.group}
+ groupHeader={(group) => {group.category}}
+ >
+ {(item) => item.title}
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/logo.stories.tsx b/packages/ui/src/components/logo.stories.tsx
new file mode 100644
index 0000000000..3f5dd9cef7
--- /dev/null
+++ b/packages/ui/src/components/logo.stories.tsx
@@ -0,0 +1,57 @@
+// @ts-nocheck
+import * as mod from "./logo"
+
+const docs = `### Overview
+OpenCode logo assets: mark, splash, and wordmark.
+
+Use Mark for compact spaces, Logo for headers, Splash for hero sections.
+
+### API
+- \`Mark\`, \`Splash\`, and \`Logo\` components accept standard SVG props.
+
+### Variants and states
+- Multiple logo variants for different contexts.
+
+### Behavior
+- Pure SVG rendering.
+
+### Accessibility
+- Provide title/aria-label when logos convey meaning.
+
+### Theming/tokens
+- Uses theme color tokens via CSS variables.
+
+`
+
+export default {
+ title: "UI/Logo",
+ id: "components-logo",
+ component: mod.Logo,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+ ),
+}
diff --git a/packages/ui/src/components/markdown.stories.tsx b/packages/ui/src/components/markdown.stories.tsx
new file mode 100644
index 0000000000..cae4294869
--- /dev/null
+++ b/packages/ui/src/components/markdown.stories.tsx
@@ -0,0 +1,53 @@
+// @ts-nocheck
+import * as mod from "./markdown"
+import { create } from "../storybook/scaffold"
+import { markdown } from "../storybook/fixtures"
+
+const docs = `### Overview
+Render sanitized Markdown with code blocks, inline code, and safe links.
+
+Pair with \`Code\` for standalone code views.
+
+### API
+- Required: \`text\` Markdown string.
+- Uses the Marked context provider for parsing and sanitization.
+
+### Variants and states
+- Code blocks include copy buttons when rendered.
+
+### Behavior
+- Sanitizes HTML and auto-converts inline URL code to links.
+- Adds copy buttons to code blocks.
+
+### Accessibility
+- Copy buttons include aria-labels from i18n.
+- TODO: confirm link target behavior in sanitized output.
+
+### Theming/tokens
+- Uses \`data-component="markdown"\` and related slots for styling.
+
+`
+
+const story = create({
+ title: "UI/Markdown",
+ mod,
+ args: {
+ text: markdown,
+ },
+})
+
+export default {
+ title: "UI/Markdown",
+ id: "components-markdown",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/message-nav.stories.tsx b/packages/ui/src/components/message-nav.stories.tsx
new file mode 100644
index 0000000000..7ce31a7bed
--- /dev/null
+++ b/packages/ui/src/components/message-nav.stories.tsx
@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./message-nav"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/MessageNav", mod })
+export default { title: "UI/MessageNav", id: "components-message-nav", component: story.meta.component }
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/message-part.stories.tsx b/packages/ui/src/components/message-part.stories.tsx
new file mode 100644
index 0000000000..28489dc7b1
--- /dev/null
+++ b/packages/ui/src/components/message-part.stories.tsx
@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./message-part"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/MessagePart", mod })
+export default { title: "UI/MessagePart", id: "components-message-part", component: story.meta.component }
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 8fbad45bd8..0f67d683f6 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -145,17 +145,22 @@ function createThrottledValue(getValue: () => string) {
return value
}
-function relativizeProjectPaths(text: string, directory?: string) {
- if (!text) return ""
- if (!directory) return text
- if (directory === "/") return text
- if (directory === "\\") return text
- return text.split(directory).join("")
+function relativizeProjectPath(path: string, directory?: string) {
+ if (!path) return ""
+ if (!directory) return path
+ if (directory === "/") return path
+ if (directory === "\\") return path
+ if (path === directory) return ""
+
+ const separator = directory.includes("\\") ? "\\" : "/"
+ const prefix = directory.endsWith(separator) ? directory : directory + separator
+ if (!path.startsWith(prefix)) return path
+ return path.slice(directory.length)
}
function getDirectory(path: string | undefined) {
const data = useData()
- return relativizeProjectPaths(_getDirectory(path), data.directory)
+ return relativizeProjectPath(_getDirectory(path), data.directory)
}
import type { IconProps } from "./icon"
@@ -1066,7 +1071,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
return items.filter((x) => !!x).join(" \u00B7 ")
})
- const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
+ const displayText = () => (part.text ?? "").trim()
const throttledText = createThrottledValue(displayText)
const isLastTextPart = createMemo(() => {
const last = (data.store.part?.[props.message.id] ?? [])
@@ -1168,7 +1173,7 @@ ToolRegistry.register({
- {i18n.t("ui.tool.loaded")} {relativizeProjectPaths(filepath, data.directory)}
+ {i18n.t("ui.tool.loaded")} {relativizeProjectPath(filepath, data.directory)}
)}
diff --git a/packages/ui/src/components/popover.stories.tsx b/packages/ui/src/components/popover.stories.tsx
new file mode 100644
index 0000000000..e5117b451b
--- /dev/null
+++ b/packages/ui/src/components/popover.stories.tsx
@@ -0,0 +1,87 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./popover"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Composable popover with optional title, description, and close button.
+
+Use for small contextual details; avoid long forms.
+
+### API
+- \`trigger\` and \`children\` define the anchor and content.
+- Optional: \`title\`, \`description\`, \`portal\`, \`open\`, \`defaultOpen\`.
+
+### Variants and states
+- Supports controlled and uncontrolled open state.
+
+### Behavior
+- Closes on outside click or Escape by default.
+
+### Accessibility
+- TODO: confirm focus management from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="popover-content"\` and related slots.
+
+`
+
+const story = create({
+ title: "UI/Popover",
+ mod,
+ args: {
+ trigger: "Open popover",
+ title: "Popover",
+ description: "Optional description",
+ defaultOpen: true,
+ children: "Popover content",
+ },
+})
+
+export default {
+ title: "UI/Popover",
+ id: "components-popover",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const NoHeader = {
+ args: {
+ title: undefined,
+ description: undefined,
+ children: "Popover body only",
+ },
+}
+
+export const Inline = {
+ args: {
+ portal: false,
+ defaultOpen: true,
+ },
+}
+
+export const Controlled = {
+ render: () => {
+ const [open, setOpen] = createSignal(true)
+ return (
+
+ Controlled content
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/progress-circle.stories.tsx b/packages/ui/src/components/progress-circle.stories.tsx
new file mode 100644
index 0000000000..5bc23c3108
--- /dev/null
+++ b/packages/ui/src/components/progress-circle.stories.tsx
@@ -0,0 +1,59 @@
+// @ts-nocheck
+import * as mod from "./progress-circle"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Circular progress indicator for compact loading states.
+
+Pair with labels for clarity in dashboards.
+
+### API
+- Required: \`percentage\` (0-100).
+- Optional: \`size\`, \`strokeWidth\`.
+
+### Variants and states
+- Single visual style; size and stroke width adjust appearance.
+
+### Behavior
+- Percentage is clamped between 0 and 100.
+
+### Accessibility
+- Use alongside text or aria-live messaging for progress context.
+
+### Theming/tokens
+- Uses \`data-component="progress-circle"\` with background/progress slots.
+
+`
+
+const story = create({ title: "UI/ProgressCircle", mod, args: { percentage: 65, size: 48 } })
+
+export default {
+ title: "UI/ProgressCircle",
+ id: "components-progress-circle",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ percentage: {
+ control: { type: "range", min: 0, max: 100, step: 1 },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const States = {
+ render: () => (
+
+
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/progress.stories.tsx b/packages/ui/src/components/progress.stories.tsx
new file mode 100644
index 0000000000..2ee3223434
--- /dev/null
+++ b/packages/ui/src/components/progress.stories.tsx
@@ -0,0 +1,67 @@
+// @ts-nocheck
+import * as mod from "./progress"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Linear progress indicator with optional label and value display.
+
+Use in forms, uploads, or background tasks.
+
+### API
+- \`value\` and \`maxValue\` control progress.
+- Optional: \`showValueLabel\`, \`hideLabel\`.
+- Children provide the label text.
+
+### Variants and states
+- Supports indeterminate state via Kobalte props (if provided).
+
+### Behavior
+- Uses Kobalte Progress for value calculation.
+
+### Accessibility
+- TODO: confirm ARIA attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="progress"\` with track/fill slots.
+
+`
+
+const story = create({
+ title: "UI/Progress",
+ mod,
+ args: {
+ value: 60,
+ maxValue: 100,
+ children: "Progress",
+ showValueLabel: true,
+ },
+})
+
+export default {
+ title: "UI/Progress",
+ id: "components-progress",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const NoLabel = {
+ args: {
+ children: "",
+ hideLabel: true,
+ showValueLabel: false,
+ value: 30,
+ },
+}
+
+export const Indeterminate = {
+ render: () => Loading,
+}
diff --git a/packages/ui/src/components/provider-icon.stories.tsx b/packages/ui/src/components/provider-icon.stories.tsx
new file mode 100644
index 0000000000..e7fc39967b
--- /dev/null
+++ b/packages/ui/src/components/provider-icon.stories.tsx
@@ -0,0 +1,69 @@
+// @ts-nocheck
+import { iconNames } from "./provider-icons/types"
+import * as mod from "./provider-icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Provider icon sprite renderer for model/provider badges.
+
+Use in model pickers or provider lists.
+
+### API
+- Required: \`id\` (provider icon name).
+- Accepts standard SVG props.
+
+### Variants and states
+- Single visual style; size via CSS.
+
+### Behavior
+- Renders from the provider SVG sprite sheet.
+
+### Accessibility
+- Provide accessible text nearby when the icon conveys meaning.
+
+### Theming/tokens
+- Uses \`data-component="provider-icon"\`.
+
+`
+
+const story = create({ title: "UI/ProviderIcon", mod, args: { id: "openai" } })
+export default {
+ title: "UI/ProviderIcon",
+ id: "components-provider-icon",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ id: {
+ control: "select",
+ options: iconNames,
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const AllIcons = {
+ render: () => (
+
+ {iconNames.map((id) => (
+
+ ))}
+
+ ),
+}
diff --git a/packages/ui/src/components/provider-icons/sprite.svg b/packages/ui/src/components/provider-icons/sprite.svg
index 88406fa8c3..29d22461d0 100644
--- a/packages/ui/src/components/provider-icons/sprite.svg
+++ b/packages/ui/src/components/provider-icons/sprite.svg
@@ -87,6 +87,18 @@
fill="currentColor"
>
+
+
+
+
@@ -175,6 +187,36 @@
fill="currentColor"
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/provider-icons/types.ts b/packages/ui/src/components/provider-icons/types.ts
index 89fbc0625f..bafa7ffaf0 100644
--- a/packages/ui/src/components/provider-icons/types.ts
+++ b/packages/ui/src/components/provider-icons/types.ts
@@ -10,6 +10,7 @@ export const iconNames = [
"xai",
"wandb",
"vultr",
+ "vivgrid",
"vercel",
"venice",
"v0",
@@ -17,11 +18,16 @@ export const iconNames = [
"togetherai",
"synthetic",
"submodel",
+ "stepfun",
+ "stackit",
"siliconflow",
"siliconflow-cn",
"scaleway",
"sap-ai-core",
"requesty",
+ "qiniu-ai",
+ "qihang-ai",
+ "privatemode-ai",
"poe",
"perplexity",
"ovhcloud",
@@ -30,19 +36,28 @@ export const iconNames = [
"openai",
"ollama-cloud",
"nvidia",
+ "novita-ai",
+ "nova",
"nebius",
"nano-gpt",
"morph",
"moonshotai",
"moonshotai-cn",
"modelscope",
+ "moark",
"mistral",
"minimax",
+ "minimax-coding-plan",
"minimax-cn",
+ "minimax-cn-coding-plan",
+ "meganova",
"lucidquery",
"lmstudio",
"llama",
+ "kuae-cloud-coding-plan",
"kimi-for-coding",
+ "kilo",
+ "jiekou",
"io-net",
"inference",
"inception",
@@ -53,9 +68,11 @@ export const iconNames = [
"google",
"google-vertex",
"google-vertex-anthropic",
+ "gitlab",
"github-models",
"github-copilot",
"friendli",
+ "firmware",
"fireworks-ai",
"fastrouter",
"deepseek",
@@ -64,8 +81,10 @@ export const iconNames = [
"cohere",
"cloudflare-workers-ai",
"cloudflare-ai-gateway",
+ "cloudferro-sherlock",
"chutes",
"cerebras",
+ "berget",
"baseten",
"bailing",
"azure",
@@ -76,6 +95,7 @@ export const iconNames = [
"alibaba-cn",
"aihubmix",
"abacus",
+ "302ai",
] as const
export type IconName = (typeof iconNames)[number]
diff --git a/packages/ui/src/components/radio-group.stories.tsx b/packages/ui/src/components/radio-group.stories.tsx
new file mode 100644
index 0000000000..4900ead846
--- /dev/null
+++ b/packages/ui/src/components/radio-group.stories.tsx
@@ -0,0 +1,92 @@
+// @ts-nocheck
+import * as mod from "./radio-group"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Segmented radio group for choosing a single option.
+
+Use for view toggles or mode selection.
+
+### API
+- Required: \`options\`.
+- Optional: \`current\`, \`defaultValue\`, \`value\`, \`label\`, \`onSelect\`.
+- Optional layout: \`size\`, \`fill\`, \`pad\`.
+
+### Variants and states
+- Size variants: small, medium.
+- Optional fill and padding behavior.
+
+### Behavior
+- Maps options to segmented items and manages selection.
+
+### Accessibility
+- TODO: confirm role/aria attributes from Kobalte SegmentedControl.
+
+### Theming/tokens
+- Uses \`data-component="radio-group"\` with size/pad data attributes.
+
+`
+
+const story = create({
+ title: "UI/RadioGroup",
+ mod,
+ args: {
+ options: ["One", "Two", "Three"],
+ defaultValue: "One",
+ },
+})
+
+export default {
+ title: "UI/RadioGroup",
+ id: "components-radio-group",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ size: {
+ control: "select",
+ options: ["small", "medium"],
+ },
+ pad: {
+ control: "select",
+ options: ["none", "normal"],
+ },
+ fill: {
+ control: "boolean",
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+ render: () => (
+
+
+
+
+ ),
+}
+
+export const Filled = {
+ args: {
+ fill: true,
+ pad: "none",
+ },
+}
+
+export const CustomLabels = {
+ render: () => (
+ (value === "list" ? "List view" : "Grid view")}
+ />
+ ),
+}
diff --git a/packages/ui/src/components/resize-handle.stories.tsx b/packages/ui/src/components/resize-handle.stories.tsx
new file mode 100644
index 0000000000..474cf71e2d
--- /dev/null
+++ b/packages/ui/src/components/resize-handle.stories.tsx
@@ -0,0 +1,156 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./resize-handle"
+
+const docs = `### Overview
+Drag handle for resizing panels or split views.
+
+Use alongside resizable panels and split layouts.
+
+### API
+- Required: \`direction\`, \`size\`, \`min\`, \`max\`, \`onResize\`.
+- Optional: \`edge\`, \`onCollapse\`, \`collapseThreshold\`.
+
+### Variants and states
+- Horizontal and vertical directions.
+
+### Behavior
+- Drag updates size and calls \`onResize\` with clamped values.
+
+### Accessibility
+- TODO: provide keyboard resizing guidance if needed.
+
+### Theming/tokens
+- Uses \`data-component="resize-handle"\` with direction/edge data attributes.
+
+`
+
+export default {
+ title: "UI/ResizeHandle",
+ id: "components-resize-handle",
+ component: mod.ResizeHandle,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => {
+ const [size, setSize] = createSignal(240)
+ return (
+
+ )
+ },
+}
+
+export const Vertical = {
+ render: () => {
+ const [size, setSize] = createSignal(180)
+ return (
+
+ )
+ },
+}
+
+export const Collapse = {
+ render: () => {
+ const [size, setSize] = createSignal(200)
+ const [collapsed, setCollapsed] = createSignal(false)
+ return (
+
+
+ {collapsed() ? "Collapsed" : `Size: ${size()}px`}
+
+
+
{
+ setCollapsed(false)
+ setSize(next)
+ }}
+ onCollapse={() => setCollapsed(true)}
+ style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
+ />
+
+ )
+ },
+}
+
+export const EdgeStart = {
+ render: () => {
+ const [size, setSize] = createSignal(240)
+ return (
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/select.stories.tsx b/packages/ui/src/components/select.stories.tsx
new file mode 100644
index 0000000000..1ee00ab851
--- /dev/null
+++ b/packages/ui/src/components/select.stories.tsx
@@ -0,0 +1,113 @@
+// @ts-nocheck
+import * as mod from "./select"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Select menu for choosing a single option with optional grouping.
+
+Use \`children\` to customize option rendering.
+
+### API
+- Required: \`options\`.
+- Optional: \`current\`, \`placeholder\`, \`value\`, \`label\`, \`groupBy\`.
+- Accepts Button props for the trigger (\`variant\`, \`size\`).
+
+### Variants and states
+- Trigger supports "settings" style via \`triggerVariant\`.
+
+### Behavior
+- Uses Kobalte Select with optional item highlight callbacks.
+
+### Accessibility
+- TODO: confirm keyboard navigation and aria attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="select"\` with slot attributes.
+
+`
+
+const story = create({
+ title: "UI/Select",
+ mod,
+ args: {
+ options: ["One", "Two", "Three"],
+ current: "One",
+ placeholder: "Choose...",
+ variant: "secondary",
+ size: "normal",
+ },
+})
+
+export default {
+ title: "UI/Select",
+ id: "components-select",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ triggerVariant: {
+ control: "select",
+ options: ["settings", undefined],
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Grouped = {
+ render: () => {
+ const options = [
+ { id: "alpha", label: "Alpha", group: "Group A" },
+ { id: "bravo", label: "Bravo", group: "Group A" },
+ { id: "delta", label: "Delta", group: "Group B" },
+ ]
+ return (
+ item.id}
+ label={(item) => item.label}
+ groupBy={(item) => item.group}
+ placeholder="Choose..."
+ variant="secondary"
+ />
+ )
+ },
+}
+
+export const SettingsTrigger = {
+ args: {
+ triggerVariant: "settings",
+ },
+}
+
+export const CustomRender = {
+ render: () => (
+
+ {(item) => {item}}
+
+ ),
+}
+
+export const CustomTriggerStyle = {
+ args: {
+ triggerStyle: { "min-width": "180px", "justify-content": "space-between" },
+ },
+}
+
+export const Disabled = {
+ args: {
+ disabled: true,
+ },
+}
diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css
index ec048d009b..fae181e20c 100644
--- a/packages/ui/src/components/session-review.css
+++ b/packages/ui/src/components/session-review.css
@@ -99,6 +99,7 @@
align-items: center;
justify-content: space-between;
width: 100%;
+ min-width: 0;
gap: 20px;
}
@@ -115,9 +116,12 @@
align-items: center;
flex-grow: 1;
min-width: 0;
+ overflow: hidden;
}
[data-slot="session-review-directory"] {
+ flex: 1 1 auto;
+ min-width: 0;
color: var(--text-base);
text-overflow: ellipsis;
overflow: hidden;
@@ -129,6 +133,11 @@
[data-slot="session-review-filename"] {
color: var(--text-strong);
flex-shrink: 0;
+ max-width: 100%;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
[data-slot="session-review-view-button"] {
@@ -163,6 +172,7 @@
gap: 16px;
align-items: center;
justify-content: flex-end;
+ margin-left: auto;
}
[data-slot="session-review-diff-chevron"] {
diff --git a/packages/ui/src/components/session-review.stories.tsx b/packages/ui/src/components/session-review.stories.tsx
new file mode 100644
index 0000000000..7ab1eb2038
--- /dev/null
+++ b/packages/ui/src/components/session-review.stories.tsx
@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./session-review"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/SessionReview", mod })
+export default { title: "UI/SessionReview", id: "components-session-review", component: story.meta.component }
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/session-turn.stories.tsx b/packages/ui/src/components/session-turn.stories.tsx
new file mode 100644
index 0000000000..927402c8db
--- /dev/null
+++ b/packages/ui/src/components/session-turn.stories.tsx
@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./session-turn"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/SessionTurn", mod })
+export default { title: "UI/SessionTurn", id: "components-session-turn", component: story.meta.component }
+export const Basic = story.Basic
diff --git a/packages/ui/src/components/spinner.stories.tsx b/packages/ui/src/components/spinner.stories.tsx
new file mode 100644
index 0000000000..be6106d148
--- /dev/null
+++ b/packages/ui/src/components/spinner.stories.tsx
@@ -0,0 +1,53 @@
+// @ts-nocheck
+import * as mod from "./spinner"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated loading indicator for inline or page-level loading states.
+
+Use with \`Button\` or in empty states.
+
+### API
+- Accepts standard SVG props (class, style).
+
+### Variants and states
+- Single default animation style.
+
+### Behavior
+- Animation is CSS-driven via data attributes.
+
+### Accessibility
+- Use alongside text or aria-live regions to convey loading state.
+
+### Theming/tokens
+- Uses \`data-component="spinner"\` for styling hooks.
+
+`
+
+const story = create({ title: "UI/Spinner", mod })
+
+export default {
+ title: "UI/Spinner",
+ id: "components-spinner",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+ render: () => (
+
+
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/sticky-accordion-header.stories.tsx b/packages/ui/src/components/sticky-accordion-header.stories.tsx
new file mode 100644
index 0000000000..3f03356267
--- /dev/null
+++ b/packages/ui/src/components/sticky-accordion-header.stories.tsx
@@ -0,0 +1,54 @@
+// @ts-nocheck
+import { Accordion } from "./accordion"
+import * as mod from "./sticky-accordion-header"
+
+const docs = `### Overview
+Sticky accordion header wrapper for persistent section labels.
+
+Use only inside \`Accordion.Item\` with \`Accordion.Trigger\`.
+
+### API
+- Accepts standard header props and children.
+
+### Variants and states
+- Inherits accordion states.
+
+### Behavior
+- Renders inside an Accordion item header.
+
+### Accessibility
+- TODO: confirm semantics from Accordion.Header usage.
+
+### Theming/tokens
+- Uses \`data-component="sticky-accordion-header"\`.
+
+`
+
+export default {
+ title: "UI/StickyAccordionHeader",
+ id: "components-sticky-accordion-header",
+ component: mod.StickyAccordionHeader,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+
+
+ Sticky header
+
+
+ Accordion content.
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/switch.stories.tsx b/packages/ui/src/components/switch.stories.tsx
new file mode 100644
index 0000000000..540e91e365
--- /dev/null
+++ b/packages/ui/src/components/switch.stories.tsx
@@ -0,0 +1,68 @@
+// @ts-nocheck
+import * as mod from "./switch"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Toggle control for binary settings.
+
+Use in settings panels or forms.
+
+### API
+- Uses Kobalte Switch props (\`checked\`, \`defaultChecked\`, \`onChange\`).
+- Optional: \`hideLabel\`, \`description\`.
+- Children render as the label.
+
+### Variants and states
+- Checked/unchecked, disabled states.
+
+### Behavior
+- Controlled or uncontrolled usage via Kobalte props.
+
+### Accessibility
+- TODO: confirm aria attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="switch"\` and slot attributes.
+
+`
+
+const story = create({
+ title: "UI/Switch",
+ mod,
+ args: { defaultChecked: true, children: "Enable notifications" },
+})
+
+export default {
+ title: "UI/Switch",
+ id: "components-switch",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const States = {
+ render: () => (
+
+ Enabled
+ Disabled
+ Disabled switch
+ With description
+
+ ),
+}
+
+export const HiddenLabel = {
+ args: {
+ children: "Hidden label",
+ hideLabel: true,
+ defaultChecked: true,
+ },
+}
diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css
index 03df9cd84e..43b74cf33e 100644
--- a/packages/ui/src/components/tabs.css
+++ b/packages/ui/src/components/tabs.css
@@ -1,4 +1,9 @@
[data-component="tabs"] {
+ --tabs-bar-height: 48px;
+ --tabs-compact-pill-height: 24px;
+ --tabs-compact-pill-radius: 6px;
+ --tabs-compact-pill-padding-x: 4px;
+
width: 100%;
height: 100%;
display: flex;
@@ -93,17 +98,6 @@
outline: none;
box-shadow: none;
}
- &:has([data-hidden]) {
- [data-slot="tabs-trigger-close-button"] {
- opacity: 0;
- }
-
- &:hover {
- [data-slot="tabs-trigger-close-button"] {
- opacity: 1;
- }
- }
- }
&:has([data-selected]) {
color: var(--text-strong);
background-color: transparent;
@@ -112,6 +106,7 @@
opacity: 1;
}
}
+
&:hover:not(:disabled):not([data-selected]) {
color: var(--text-strong);
}
@@ -140,6 +135,118 @@
}
}
+ #review-panel &[data-variant="normal"][data-orientation="horizontal"] {
+ background-color: var(--background-stronger);
+
+ [data-slot="tabs-list"] {
+ height: var(--tabs-bar-height);
+ padding-left: 12px;
+ padding-right: 0;
+ --tabs-review-gap: 16px;
+ --tabs-review-fade: 16px;
+ gap: var(--tabs-review-gap);
+ background-color: var(--background-stronger);
+ border-bottom: 1px solid var(--border-weak-base);
+
+ &::after {
+ display: none;
+ }
+
+ > .sticky {
+ border-bottom: none;
+ background-color: var(--background-stronger);
+
+ &::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: calc(var(--tabs-review-fade) * -1);
+ width: var(--tabs-review-fade);
+ pointer-events: none;
+ background: linear-gradient(90deg, transparent, var(--background-stronger));
+ }
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ height: var(--tabs-compact-pill-height);
+ margin-block: calc((var(--tabs-bar-height) - var(--tabs-compact-pill-height)) / 2);
+ max-width: 320px;
+ padding-inline: var(--tabs-compact-pill-padding-x);
+ box-sizing: border-box;
+ border: 1px solid transparent;
+ border-radius: var(--tabs-compact-pill-radius);
+ background-color: transparent;
+ gap: 8px;
+ color: var(--text-weak);
+ transition:
+ color 120ms ease,
+ background-color 120ms ease,
+ border-color 120ms ease;
+
+ &::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: calc((var(--tabs-compact-pill-height) - var(--tabs-bar-height)) / 2);
+ height: 1px;
+ background-color: var(--text-strong);
+ opacity: 0;
+ transform: scaleX(0.75);
+ transform-origin: center;
+ transition:
+ opacity 120ms ease,
+ transform 120ms ease;
+ }
+
+ &[data-value="review"] {
+ padding-left: 8px;
+ padding-right: 8px;
+ }
+
+ [data-slot="tabs-trigger"] {
+ height: 100%;
+ padding: 0 !important;
+ }
+
+ &:has([data-slot="tabs-trigger-close-button"]) {
+ padding-right: 5px;
+ [data-slot="tabs-trigger"] {
+ padding-right: 0 !important;
+ }
+ }
+
+ &:has([data-selected]) {
+ color: var(--text-strong);
+ background-color: var(--surface-base-active);
+ border-color: var(--border-weak-base);
+
+ &::after {
+ opacity: 1;
+ transform: scaleX(1);
+ }
+ }
+
+ [data-component="file-icon"] {
+ filter: grayscale(1) !important;
+ transition: filter 120ms ease;
+ }
+
+ &:has([data-selected]) {
+ [data-component="file-icon"] {
+ filter: grayscale(0) !important;
+ }
+ }
+
+ &:hover:not(:disabled):not(:has([data-selected])) {
+ color: var(--text-base);
+ background-color: var(--surface-base-hover);
+ }
+ }
+ }
+
&[data-variant="alt"] {
[data-slot="tabs-list"] {
padding-left: 24px;
@@ -282,9 +389,15 @@
}
[data-slot="tabs-trigger-wrapper"] {
- height: 24px;
- border-radius: 6px;
+ height: var(--tabs-compact-pill-height);
+ border-radius: var(--tabs-compact-pill-radius);
color: var(--text-weak);
+ box-sizing: border-box;
+ border: 1px solid transparent;
+ transition:
+ color 120ms ease,
+ background-color 120ms ease,
+ border-color 120ms ease;
&:not(:has([data-selected])):hover:not(:disabled) {
color: var(--text-base);
@@ -292,6 +405,7 @@
&:has([data-selected]) {
color: var(--text-strong);
+ border-color: var(--border-weak-base);
}
}
}
@@ -459,3 +573,41 @@
}
}
}
+
+[data-component="tabs-drag-preview"] {
+ position: relative;
+ display: flex;
+ align-items: center;
+ height: var(--tabs-bar-height, 48px);
+ max-width: 320px;
+ padding-inline: var(--tabs-compact-pill-padding-x, 4px);
+ overflow: hidden;
+ color: var(--text-strong);
+ opacity: 0.6;
+}
+
+[data-component="tabs-drag-preview"]::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: calc((var(--tabs-bar-height, 48px) - var(--tabs-compact-pill-height, 24px)) / 2);
+ height: var(--tabs-compact-pill-height, 24px);
+ border: 1px solid var(--border-weak-base);
+ border-radius: var(--tabs-compact-pill-radius, 6px);
+ background-color: var(--surface-base-active);
+}
+
+[data-component="tabs-drag-preview"]::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 1px;
+ background-color: var(--text-strong);
+}
+
+[data-component="tabs-drag-preview"] > * {
+ position: relative;
+}
diff --git a/packages/ui/src/components/tabs.stories.tsx b/packages/ui/src/components/tabs.stories.tsx
new file mode 100644
index 0000000000..8e09764dcc
--- /dev/null
+++ b/packages/ui/src/components/tabs.stories.tsx
@@ -0,0 +1,179 @@
+// @ts-nocheck
+import { IconButton } from "./icon-button"
+import { createSignal } from "solid-js"
+import * as mod from "./tabs"
+
+const docs = `### Overview
+Tabbed navigation for switching between related panels.
+
+Compose \`Tabs.List\` + \`Tabs.Trigger\` + \`Tabs.Content\`.
+
+### API
+- Root accepts Kobalte Tabs props (\`value\`, \`defaultValue\`, \`onChange\`).
+- \`variant\` sets visual style: normal, alt, pill, settings.
+- \`orientation\` supports horizontal or vertical layouts.
+- Trigger supports \`closeButton\`, \`hideCloseButton\`, and \`onMiddleClick\`.
+
+### Variants and states
+- Normal, alt, pill, settings variants.
+- Horizontal and vertical orientations.
+
+### Behavior
+- Uses Kobalte Tabs for roving focus and selection management.
+
+### Accessibility
+- TODO: confirm keyboard interactions from Kobalte Tabs.
+
+### Theming/tokens
+- Uses \`data-component="tabs"\` with variant/orientation data attributes.
+
+`
+
+export default {
+ title: "UI/Tabs",
+ id: "components-tabs",
+ component: mod.Tabs,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ variant: {
+ control: "select",
+ options: ["normal", "alt", "pill", "settings"],
+ },
+ orientation: {
+ control: "select",
+ options: ["horizontal", "vertical"],
+ },
+ },
+}
+
+export const Basic = {
+ args: {
+ variant: "normal",
+ orientation: "horizontal",
+ defaultValue: "overview",
+ },
+ render: (props) => (
+
+
+ Overview
+ Details
+ Activity
+
+ Overview content
+ Details content
+ Activity content
+
+ ),
+}
+
+export const Settings = {
+ args: {
+ variant: "settings",
+ orientation: "horizontal",
+ defaultValue: "general",
+ },
+ render: (props) => (
+
+
+ General
+ Appearance
+
+ General settings
+ Appearance settings
+
+ ),
+}
+
+export const Alt = {
+ args: {
+ variant: "alt",
+ orientation: "horizontal",
+ defaultValue: "first",
+ },
+ render: (props) => (
+
+
+ First
+ Second
+
+ Alt content
+ Alt content 2
+
+ ),
+}
+
+export const Vertical = {
+ args: {
+ variant: "pill",
+ orientation: "vertical",
+ defaultValue: "alpha",
+ },
+ render: (props) => (
+
+
+ Alpha
+ Beta
+
+ Alpha content
+ Beta content
+
+ ),
+}
+
+export const Closable = {
+ args: {
+ variant: "normal",
+ orientation: "horizontal",
+ defaultValue: "tab-1",
+ },
+ render: (props) => (
+
+
+ }
+ >
+ Tab 1
+
+ Tab 2
+
+ Closable content
+ Standard content
+
+ ),
+}
+
+export const MiddleClick = {
+ args: {
+ variant: "normal",
+ orientation: "horizontal",
+ defaultValue: "tab-1",
+ },
+ render: (props) => {
+ const [message, setMessage] = createSignal("Middle click a tab")
+ return (
+
+
{message()}
+
+
+ setMessage("Middle clicked tab-1")}>
+ Tab 1
+
+ setMessage("Middle clicked tab-2")}>
+ Tab 2
+
+
+ Tab 1 content
+ Tab 2 content
+
+
+ )
+ },
+}
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index 4836a0864c..396504dd72 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -61,10 +61,16 @@ function TabsTrigger(props: ParentProps) {
return (
{
+ if (e.button === 1 && split.onMiddleClick) {
+ e.preventDefault()
+ }
+ }}
onAuxClick={(e) => {
if (e.button === 1 && split.onMiddleClick) {
e.preventDefault()
@@ -75,6 +81,7 @@ function TabsTrigger(props: ParentProps
) {
{split.children}
diff --git a/packages/ui/src/components/tag.stories.tsx b/packages/ui/src/components/tag.stories.tsx
new file mode 100644
index 0000000000..73ae880ba1
--- /dev/null
+++ b/packages/ui/src/components/tag.stories.tsx
@@ -0,0 +1,58 @@
+// @ts-nocheck
+import * as mod from "./tag"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Small label tag for metadata and status chips.
+
+Use alongside headings or lists for quick metadata.
+
+### API
+- Optional: \`size\` (normal | large).
+- Accepts standard span props.
+
+### Variants and states
+- Size variants only.
+
+### Behavior
+- Inline element; size controls padding and font size via CSS.
+
+### Accessibility
+- Ensure text conveys meaning; avoid color-only distinction.
+
+### Theming/tokens
+- Uses \`data-component="tag"\` with size data attributes.
+
+`
+
+const story = create({ title: "UI/Tag", mod, args: { children: "Tag" } })
+export default {
+ title: "UI/Tag",
+ id: "components-tag",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+ argTypes: {
+ size: {
+ control: "select",
+ options: ["normal", "large"],
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+ render: () => (
+
+ Normal
+ Large
+
+ ),
+}
diff --git a/packages/ui/src/components/text-field.stories.tsx b/packages/ui/src/components/text-field.stories.tsx
new file mode 100644
index 0000000000..73f9006607
--- /dev/null
+++ b/packages/ui/src/components/text-field.stories.tsx
@@ -0,0 +1,111 @@
+// @ts-nocheck
+import * as mod from "./text-field"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Text input with label, description, and optional copy-to-clipboard action.
+
+Pair with \`Tooltip\` and \`IconButton\` for copy affordance (built in).
+
+### API
+- Supports Kobalte TextField props: \`value\`, \`defaultValue\`, \`onChange\`, \`disabled\`, \`readOnly\`.
+- Optional: \`label\`, \`description\`, \`error\`, \`variant\`, \`copyable\`, \`multiline\`.
+
+### Variants and states
+- Normal and ghost variants.
+- Supports multiline textarea.
+
+### Behavior
+- When \`copyable\` is true, clicking copies the current value.
+
+### Accessibility
+- Label is hidden when \`hideLabel\` is true (sr-only).
+
+### Theming/tokens
+- Uses \`data-component="input"\` with slot attributes for styling.
+
+`
+
+const story = create({
+ title: "UI/TextField",
+ mod,
+ args: {
+ label: "Label",
+ placeholder: "Type here...",
+ defaultValue: "Hello",
+ },
+})
+
+export default {
+ title: "UI/TextField",
+ id: "components-text-field",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Variants = {
+ render: () => (
+
+
+
+
+ ),
+}
+
+export const Multiline = {
+ args: {
+ label: "Description",
+ multiline: true,
+ defaultValue: "Line one\nLine two",
+ },
+}
+
+export const Copyable = {
+ args: {
+ label: "Invite link",
+ defaultValue: "https://example.com/invite/abc",
+ copyable: true,
+ copyKind: "link",
+ },
+}
+
+export const Error = {
+ args: {
+ label: "Email",
+ defaultValue: "invalid@",
+ error: "Enter a valid email address",
+ },
+}
+
+export const Disabled = {
+ args: {
+ label: "Disabled",
+ defaultValue: "Readonly",
+ disabled: true,
+ },
+}
+
+export const ReadOnly = {
+ args: {
+ label: "Read only",
+ defaultValue: "Read only value",
+ readOnly: true,
+ },
+}
+
+export const HiddenLabel = {
+ args: {
+ label: "Hidden label",
+ hideLabel: true,
+ placeholder: "Hidden label",
+ },
+}
diff --git a/packages/ui/src/components/text-shimmer.stories.tsx b/packages/ui/src/components/text-shimmer.stories.tsx
new file mode 100644
index 0000000000..4b6de34c2e
--- /dev/null
+++ b/packages/ui/src/components/text-shimmer.stories.tsx
@@ -0,0 +1,59 @@
+// @ts-nocheck
+import * as mod from "./text-shimmer"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated shimmer effect for loading text placeholders.
+
+Use for pending states inside buttons or list rows.
+
+### API
+- Required: \`text\` string.
+- Optional: \`as\`, \`active\`, \`stepMs\`, \`durationMs\`.
+
+### Variants and states
+- Active/inactive state via \`active\`.
+
+### Behavior
+- Characters animate with staggered delays.
+
+### Accessibility
+- Uses \`aria-label\` with the full text.
+
+### Theming/tokens
+- Uses \`data-component="text-shimmer"\` and CSS custom properties for timing.
+
+`
+
+const story = create({ title: "UI/TextShimmer", mod, args: { text: "Loading..." } })
+
+export default {
+ title: "UI/TextShimmer",
+ id: "components-text-shimmer",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Inactive = {
+ args: {
+ text: "Static text",
+ active: false,
+ },
+}
+
+export const CustomTiming = {
+ args: {
+ text: "Custom timing",
+ stepMs: 80,
+ durationMs: 1800,
+ },
+}
diff --git a/packages/ui/src/components/toast.stories.tsx b/packages/ui/src/components/toast.stories.tsx
new file mode 100644
index 0000000000..ef9cbb68ef
--- /dev/null
+++ b/packages/ui/src/components/toast.stories.tsx
@@ -0,0 +1,138 @@
+// @ts-nocheck
+import * as mod from "./toast"
+import { Button } from "./button"
+
+const docs = `### Overview
+Toast notifications with optional icons, actions, and progress.
+
+Use brief titles/descriptions; limit actions to 1-2.
+
+### API
+- Use \`showToast\` or \`showPromiseToast\` to trigger toasts.
+- Render \`Toast.Region\` once per page.
+- \`Toast\` subcomponents compose the structure.
+
+### Variants and states
+- Variants: default, success, error, loading.
+- Optional actions and persistent toasts.
+
+### Behavior
+- Toasts render in a portal and auto-dismiss unless persistent.
+
+### Accessibility
+- TODO: confirm aria-live behavior from Kobalte Toast.
+
+### Theming/tokens
+- Uses \`data-component="toast"\` and slot data attributes.
+
+`
+
+export default {
+ title: "UI/Toast",
+ id: "components-toast",
+ component: mod.Toast,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = {
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const Actions = {
+ render: () => (
+
+
+
+
+ ),
+}
+
+export const Promise = {
+ render: () => (
+
+
+
+
+ ),
+}
+
+export const Loading = {
+ render: () => (
+
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/tooltip.stories.tsx b/packages/ui/src/components/tooltip.stories.tsx
new file mode 100644
index 0000000000..efe11d92ef
--- /dev/null
+++ b/packages/ui/src/components/tooltip.stories.tsx
@@ -0,0 +1,64 @@
+// @ts-nocheck
+import * as mod from "./tooltip"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Tooltip for contextual hints and keybind callouts.
+
+Use for short hints; avoid long descriptions.
+
+### API
+- Required: \`value\` (tooltip content).
+- Optional: \`inactive\`, \`forceOpen\`, placement props from Kobalte.
+
+### Variants and states
+- Supports keybind-style tooltip via \`TooltipKeybind\`.
+
+### Behavior
+- Opens on hover/focus; can be forced open.
+
+### Accessibility
+- TODO: confirm trigger semantics and focus behavior.
+
+### Theming/tokens
+- Uses \`data-component="tooltip"\` and related slots.
+
+`
+
+const story = create({ title: "UI/Tooltip", mod, args: { value: "Tooltip", children: "Hover me" } })
+
+export default {
+ title: "UI/Tooltip",
+ id: "components-tooltip",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Keybind = {
+ render: () => (
+
+ Hover for keybind
+
+ ),
+}
+
+export const ForcedOpen = {
+ args: {
+ forceOpen: true,
+ },
+}
+
+export const Inactive = {
+ args: {
+ inactive: true,
+ },
+}
diff --git a/packages/ui/src/components/typewriter.stories.tsx b/packages/ui/src/components/typewriter.stories.tsx
new file mode 100644
index 0000000000..880ca74894
--- /dev/null
+++ b/packages/ui/src/components/typewriter.stories.tsx
@@ -0,0 +1,51 @@
+// @ts-nocheck
+import * as mod from "./typewriter"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated typewriter text effect for short inline messages.
+
+Use for short status lines; avoid long paragraphs.
+
+### API
+- Optional: \`text\` string; if absent, nothing is rendered.
+- Optional: \`as\` to change the rendered element.
+
+### Variants and states
+- Single animation style with cursor blink.
+
+### Behavior
+- Types one character at a time with randomized delays.
+
+### Accessibility
+- TODO: confirm if cursor should be aria-hidden in all contexts.
+
+### Theming/tokens
+- Uses \`blinking-cursor\` class for cursor styling.
+
+`
+
+const story = create({ title: "UI/Typewriter", mod, args: { text: "Typewriter text" } })
+
+export default {
+ title: "UI/Typewriter",
+ id: "components-typewriter",
+ component: story.meta.component,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: docs,
+ },
+ },
+ },
+}
+
+export const Basic = story.Basic
+
+export const Inline = {
+ args: {
+ text: "Inline typewriter",
+ as: "span",
+ },
+}
diff --git a/packages/ui/src/storybook/fixtures.ts b/packages/ui/src/storybook/fixtures.ts
new file mode 100644
index 0000000000..59d4129709
--- /dev/null
+++ b/packages/ui/src/storybook/fixtures.ts
@@ -0,0 +1,51 @@
+export const diff = {
+ before: {
+ name: "src/greet.ts",
+ contents: `export function greet(name: string) {
+ return \`Hello, \${name}!\`
+}
+`,
+ },
+ after: {
+ name: "src/greet.ts",
+ contents: `export function greet(name: string, excited = false) {
+ const message = \`Hello, \${name}!\`
+ return excited ? \`\${message}!!\` : message
+}
+`,
+ },
+}
+
+export const code = {
+ name: "src/calc.ts",
+ contents: `export function sum(values: number[]) {
+ return values.reduce((total, value) => total + value, 0)
+}
+
+export function average(values: number[]) {
+ if (values.length === 0) return 0
+ return sum(values) / values.length
+}
+`,
+}
+
+export const markdown = [
+ "# Markdown",
+ "",
+ "Use **Markdown** for rich text.",
+ "",
+ "## Highlights",
+ "- Headings, lists, and code blocks",
+ "- Inline `code` and links",
+ "",
+ "```ts",
+ "export const value = 42",
+ "```",
+ "",
+ "More at https://example.com/docs",
+].join("\n")
+
+export const changes = {
+ additions: 18,
+ deletions: 6,
+}
diff --git a/packages/ui/src/storybook/scaffold.tsx b/packages/ui/src/storybook/scaffold.tsx
new file mode 100644
index 0000000000..2512aa09be
--- /dev/null
+++ b/packages/ui/src/storybook/scaffold.tsx
@@ -0,0 +1,62 @@
+import { ErrorBoundary, type ValidComponent } from "solid-js"
+import { Dynamic } from "solid-js/web"
+
+function fn(value: unknown): value is (...args: never[]) => unknown {
+ return typeof value === "function"
+}
+
+function pick(mod: Record, name?: string) {
+ if (name && fn(mod[name])) return mod[name]
+ if (fn(mod.default)) return mod.default
+
+ const preferred = Object.keys(mod)
+ .filter((k) => k[0] && k[0] === k[0].toUpperCase())
+ .find((k) => fn(mod[k]))
+ if (preferred) return mod[preferred]
+
+ const first = Object.keys(mod).find((k) => fn(mod[k]))
+ if (first) return mod[first]
+
+ return () => {
+ return (
+
+
Missing component export.
+
Exports: {Object.keys(mod).join(", ") || "(none)"}
+
+ )
+ }
+}
+
+export function create(input: {
+ title: string
+ mod: Record
+ name?: string
+ args?: Record
+}) {
+ const component = pick(input.mod, input.name) as unknown as ValidComponent
+
+ return {
+ meta: {
+ title: input.title,
+ component,
+ },
+ Basic: {
+ args: input.args ?? {},
+ render: (args: Record) => {
+ return (
+ {
+ return (
+
+ {String(err)}
+
+ )
+ }}
+ >
+
+
+ )
+ },
+ },
+ }
+}
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index 832f7cbf81..25f6a3dcc4 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -17,5 +17,6 @@
// Type Checking & Safety
"strict": true,
"types": ["vite/client", "bun"]
- }
+ },
+ "exclude": ["**/*.stories.*", "**/*.mdx"]
}
diff --git a/packages/util/package.json b/packages/util/package.json
index d389d3ade1..36a235639e 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.2.14",
+ "version": "1.2.15",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/web/package.json b/packages/web/package.json
index 12bffe86d6..daf2ad3480 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
- "version": "1.2.14",
+ "version": "1.2.15",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/packages/web/src/content/docs/zh-cn/custom-tools.mdx b/packages/web/src/content/docs/zh-cn/custom-tools.mdx
index 81a90a2bcb..8b44a0450c 100644
--- a/packages/web/src/content/docs/zh-cn/custom-tools.mdx
+++ b/packages/web/src/content/docs/zh-cn/custom-tools.mdx
@@ -79,6 +79,32 @@ export const multiply = tool({
---
+#### 与内置工具的名称冲突
+
+自定义工具通过工具名称进行索引。如果自定义工具使用了与内置工具相同的名称,则优先使用自定义工具。
+
+例如,这个文件取代了内置的bash工具:
+
+```ts title=".opencode/tools/bash.ts"
+import { tool } from "@opencode-ai/plugin"
+
+export default tool({
+ description: "Restricted bash wrapper",
+ args: {
+ command: tool.schema.string(),
+ },
+ async execute(args) {
+ return `blocked: ${args.command}`
+ },
+})
+```
+
+:::note
+除非你有意替换内置工具,否则最好用独特的名字。如果你想禁用内置工具但不想覆盖它,使用 [权限](/docs/permissions).
+:::
+
+---
+
### 参数
你可以使用 `tool.schema`(即 [Zod](https://zod.dev))来定义参数类型。
diff --git a/packages/web/src/content/docs/zh-cn/lsp.mdx b/packages/web/src/content/docs/zh-cn/lsp.mdx
index 57b8121902..59dd7082a1 100644
--- a/packages/web/src/content/docs/zh-cn/lsp.mdx
+++ b/packages/web/src/content/docs/zh-cn/lsp.mdx
@@ -27,6 +27,7 @@ OpenCode 内置了多种适用于主流语言的 LSP 服务器:
| gopls | .go | 需要 `go` 命令可用 |
| hls | .hs, .lhs | 需要 `haskell-language-server-wrapper` 命令可用 |
| jdtls | .java | 需要已安装 `Java SDK (version 21+)` |
+| julials | .jl | 需要安装 `julia` and `LanguageServer.jl` |
| kotlin-ls | .kt, .kts | 为 Kotlin 项目自动安装 |
| lua-ls | .lua | 为 Lua 项目自动安装 |
| nixd | .nix | 需要 `nixd` 命令可用 |
diff --git a/packages/web/src/content/docs/zh-cn/plugins.mdx b/packages/web/src/content/docs/zh-cn/plugins.mdx
index 0df6d1ee65..e8a8bd70cb 100644
--- a/packages/web/src/content/docs/zh-cn/plugins.mdx
+++ b/packages/web/src/content/docs/zh-cn/plugins.mdx
@@ -307,6 +307,10 @@ export const CustomToolsPlugin: Plugin = async (ctx) => {
你的自定义工具将与内置工具一起在 OpenCode 中可用。
+:::note
+如果插件工具与内置工具使用相同的名称,则优先使用插件工具。
+:::
+
---
### 日志记录
diff --git a/packages/web/src/content/docs/zh-cn/providers.mdx b/packages/web/src/content/docs/zh-cn/providers.mdx
index ccc2bf7d40..9c1616876d 100644
--- a/packages/web/src/content/docs/zh-cn/providers.mdx
+++ b/packages/web/src/content/docs/zh-cn/providers.mdx
@@ -131,6 +131,8 @@ OpenCode Zen 是由 OpenCode 团队提供的模型列表,这些模型已经过
2. 使用以下方法之一**配置身份验证**:
+ ***
+
#### 环境变量(快速上手)
运行 opencode 时设置以下环境变量之一:
@@ -153,6 +155,8 @@ OpenCode Zen 是由 OpenCode 团队提供的模型列表,这些模型已经过
export AWS_REGION=us-east-1
```
+ ***
+
#### 配置文件(推荐)
如需项目级别或持久化的配置,请使用 `opencode.json`:
@@ -180,6 +184,8 @@ OpenCode Zen 是由 OpenCode 团队提供的模型列表,这些模型已经过
配置文件中的选项优先级高于环境变量。
:::
+ ***
+
#### 进阶:VPC 端点
如果你使用 Bedrock 的 VPC 端点:
@@ -203,12 +209,16 @@ OpenCode Zen 是由 OpenCode 团队提供的模型列表,这些模型已经过
`endpoint` 选项是通用 `baseURL` 选项的别名,使用了 AWS 特有的术语。如果同时指定了 `endpoint` 和 `baseURL`,则 `endpoint` 优先。
:::
+ ***
+
#### 认证方式
- **`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**:在 AWS 控制台中创建 IAM 用户并生成访问密钥
- **`AWS_PROFILE`**:使用 `~/.aws/credentials` 中的命名配置文件。需要先通过 `aws configure --profile my-profile` 或 `aws sso login` 进行配置
- **`AWS_BEARER_TOKEN_BEDROCK`**:从 Amazon Bedrock 控制台生成长期 API 密钥
- **`AWS_WEB_IDENTITY_TOKEN_FILE` / `AWS_ROLE_ARN`**:适用于 EKS IRSA(服务账户的 IAM 角色)或其他支持 OIDC 联合的 Kubernetes 环境。使用服务账户注解时,Kubernetes 会自动注入这些环境变量。
+ ***
+
#### 认证优先级
Amazon Bedrock 使用以下认证优先级:
diff --git a/packages/web/src/content/docs/zh-cn/tui.mdx b/packages/web/src/content/docs/zh-cn/tui.mdx
index e34c088cb3..df8ce38fec 100644
--- a/packages/web/src/content/docs/zh-cn/tui.mdx
+++ b/packages/web/src/content/docs/zh-cn/tui.mdx
@@ -234,7 +234,7 @@ How is auth handled in @packages/functions/src/api/index.ts?
列出可用主题。
```bash frame="none"
-/theme
+/themes
```
**快捷键:** `ctrl+x t`
diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx
index 39358c4170..e3fe35e867 100644
--- a/packages/web/src/content/docs/zh-cn/zen.mdx
+++ b/packages/web/src/content/docs/zh-cn/zen.mdx
@@ -64,19 +64,22 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
+| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
+| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
+| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
+| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
-| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
-| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
-| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
+| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
+| GLM 5 Free | glm-5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -104,42 +107,47 @@ https://opencode.ai/zen/v1/models
我们支持按量付费模式。以下是**每 100 万 Token** 的价格。
-| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 |
-| -------------------------------- | ------ | ------ | -------- | -------- |
-| Big Pickle | 免费 | 免费 | 免费 | - |
-| MiniMax M2.5 Free | 免费 | 免费 | 免费 | - |
-| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
-| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
-| GLM 5 | $1.00 | $3.20 | $0.20 | - |
-| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
-| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
-| Kimi K2.5 Free | 免费 | 免费 | 免费 | - |
-| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
-| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
-| Kimi K2 | $0.40 | $2.50 | - | - |
-| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
-| Claude Sonnet 4.5 (≤ 200K Token) | $3.00 | $15.00 | $0.30 | $3.75 |
-| Claude Sonnet 4.5 (> 200K Token) | $6.00 | $22.50 | $0.60 | $7.50 |
-| Claude Sonnet 4 (≤ 200K Token) | $3.00 | $15.00 | $0.30 | $3.75 |
-| Claude Sonnet 4 (> 200K Token) | $6.00 | $22.50 | $0.60 | $7.50 |
-| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
-| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 |
-| Claude Opus 4.6 (≤ 200K Token) | $5.00 | $25.00 | $0.50 | $6.25 |
-| Claude Opus 4.6 (> 200K Token) | $10.00 | $37.50 | $1.00 | $12.50 |
-| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 |
-| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 |
-| Gemini 3 Pro (≤ 200K Token) | $2.00 | $12.00 | $0.20 | - |
-| Gemini 3 Pro (> 200K Token) | $4.00 | $18.00 | $0.40 | - |
-| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
-| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
-| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
-| GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
-| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
-| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - |
-| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - |
-| GPT 5 | $1.07 | $8.50 | $0.107 | - |
-| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
-| GPT 5 Nano | 免费 | 免费 | 免费 | - |
+| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 |
+| --------------------------------- | ------ | ------ | -------- | -------- |
+| Big Pickle | 免费 | 免费 | 免费 | - |
+| MiniMax M2.5 Free | 免费 | 免费 | 免费 | - |
+| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
+| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
+| GLM 5 Free | Free | Free | Free | - |
+| GLM 5 | $1.00 | $3.20 | $0.20 | - |
+| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
+| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
+| Kimi K2.5 Free | 免费 | 免费 | 免费 | - |
+| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
+| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
+| Kimi K2 | $0.40 | $2.50 | - | - |
+| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
+| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 |
+| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 |
+| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 |
+| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 |
+| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
+| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
+| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
+| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
+| Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
+| Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
+| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
+| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 |
+| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
+| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
+| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
+| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
+| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
+| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
+| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
+| GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
+| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
+| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - |
+| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - |
+| GPT 5 | $1.07 | $8.50 | $0.107 | - |
+| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
+| GPT 5 Nano | 免费 | 免费 | 免费 | - |
你可能会在使用记录中看到 _Claude Haiku 3.5_。这是一个[低成本模型](/docs/config/#models),用于生成会话标题。
@@ -149,6 +157,7 @@ https://opencode.ai/zen/v1/models
免费模型说明:
+- GLM 5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Kimi K2.5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- MiniMax M2.5 Free 在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Big Pickle 是一个隐身模型,在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
@@ -178,6 +187,7 @@ https://opencode.ai/zen/v1/models
我们所有的模型都托管在美国。我们的提供商遵循零保留政策,不会将你的数据用于模型训练,但以下情况除外:
- Big Pickle:在免费期间,收集的数据可能会被用于改进模型。
+- GLM 5 Free:在免费期间,收集的数据可能会被用于改进模型。
- Kimi K2.5 Free:在免费期间,收集的数据可能会被用于改进模型。
- MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。
- OpenAI API:请求会根据 [OpenAI 数据政策](https://platform.openai.com/docs/guides/your-data)保留 30 天。
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index a661b25d80..a041b65223 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.2.14",
+ "version": "1.2.15",
"publisher": "sst-dev",
"repository": {
"type": "git",