diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td
index 412716a18d..28535b5779 100644
--- a/.github/VOUCHED.td
+++ b/.github/VOUCHED.td
@@ -8,7 +8,9 @@
# - Denounce with minus prefix: -username or -platform:username.
# - Optional details after a space following the handle.
adamdotdevin
+-agusbasari29 AI PR slop
ariane-emory
+edemaine
-florianleibert
fwang
iamdavidhill
diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml
index 20d53e81e8..6c632f7e07 100644
--- a/.github/actions/setup-bun/action.yml
+++ b/.github/actions/setup-bun/action.yml
@@ -11,10 +11,25 @@ runs:
restore-keys: |
${{ runner.os }}-bun-
+ - name: Get baseline download URL
+ id: bun-url
+ 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/bun-v${V}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
+ fi
+
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
- bun-version-file: package.json
+ bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
+ bun-download-url: ${{ steps.bun-url.outputs.url }}
- name: Install dependencies
run: bun install
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 20d2bc18d8..a7106667b1 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -27,7 +27,11 @@ jobs:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+ - name: Install OpenCode
+ run: bun i -g opencode-ai
+
- name: Sync beta branch
env:
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
run: bun script/beta.ts
diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml
index 5b424d0adf..c3bcf9f686 100644
--- a/.github/workflows/compliance-close.yml
+++ b/.github/workflows/compliance-close.yml
@@ -65,6 +65,15 @@ jobs:
body: closeMessage,
});
+ try {
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: item.number,
+ name: 'needs:compliance',
+ });
+ } catch (e) {}
+
if (isPR) {
await github.rest.pulls.update({
owner: context.repo.owner,
diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml
index 8cd0cc52e2..fff2ec4292 100644
--- a/.github/workflows/docs-locale-sync.yml
+++ b/.github/workflows/docs-locale-sync.yml
@@ -12,13 +12,14 @@ jobs:
if: github.actor != 'opencode-agent[bot]'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
- id-token: write
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
+ persist-credentials: false
fetch-depth: 0
+ ref: ${{ github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
@@ -46,15 +47,26 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"
+ - name: Install OpenCode
+ if: steps.changes.outputs.has_changes == 'true'
+ run: curl -fsSL https://opencode.ai/install | bash
+
- name: Sync locale docs with OpenCode
if: steps.changes.outputs.has_changes == 'true'
- uses: sst/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
- with:
- model: opencode/gpt-5.2
- agent: docs
- prompt: |
+ OPENCODE_CONFIG_CONTENT: |
+ {
+ "permission": {
+ "*": "deny",
+ "read": "allow",
+ "edit": "allow",
+ "glob": "allow",
+ "task": "allow"
+ }
+ }
+ run: |
+ opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
Update localized docs to match the latest English docs changes.
Changed English doc files:
@@ -67,10 +79,12 @@ jobs:
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
- 5. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
- 6. Keep locale docs structure aligned with their corresponding English pages.
- 7. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
- 8. If no locale updates are needed, make no changes.
+ 5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
+ 6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
+ 7. Keep locale docs structure aligned with their corresponding English pages.
+ 8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
+ 9. If no locale updates are needed, make no changes.
+ EOF
- name: Commit and push locale docs updates
if: steps.changes.outputs.has_changes == 'true'
diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml
index 27581d06b7..1edbd5d061 100644
--- a/.github/workflows/pr-standards.yml
+++ b/.github/workflows/pr-standards.yml
@@ -108,11 +108,11 @@ jobs:
await removeLabel('needs:title');
- // Step 2: Check for linked issue (skip for docs/refactor PRs)
- const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
+ // Step 2: Check for linked issue (skip for docs/refactor/feat PRs)
+ const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
if (skipIssueCheck) {
await removeLabel('needs:issue');
- console.log('Skipping issue check for docs/refactor PR');
+ console.log('Skipping issue check for docs/refactor/feat PR');
return;
}
const query = `
@@ -189,7 +189,7 @@ jobs:
const body = pr.body || '';
const title = pr.title;
- const isDocsOrRefactor = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
+ const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
const issues = [];
@@ -225,8 +225,8 @@ jobs:
}
}
- // Check: issue reference (skip for docs/refactor)
- if (!isDocsOrRefactor && hasIssueSection) {
+ // Check: issue reference (skip for docs/refactor/feat)
+ if (!isDocsRefactorOrFeat && hasIssueSection) {
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
const issueContent = issueMatch ? issueMatch[1].trim() : '';
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 8d4c9038a7..0dbd04f821 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -99,7 +99,6 @@ jobs:
with:
name: opencode-cli
path: packages/opencode/dist
-
outputs:
version: ${{ needs.version.outputs.version }}
@@ -240,11 +239,131 @@ jobs:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
+ build-electron:
+ needs:
+ - build-cli
+ - version
+ continue-on-error: false
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - host: macos-latest
+ target: x86_64-apple-darwin
+ platform_flag: --mac --x64
+ - host: macos-latest
+ target: aarch64-apple-darwin
+ platform_flag: --mac --arm64
+ - host: "blacksmith-4vcpu-windows-2025"
+ target: x86_64-pc-windows-msvc
+ platform_flag: --win
+ - host: "blacksmith-4vcpu-ubuntu-2404"
+ target: x86_64-unknown-linux-gnu
+ platform_flag: --linux
+ - host: "blacksmith-4vcpu-ubuntu-2404"
+ target: aarch64-unknown-linux-gnu
+ platform_flag: --linux
+ runs-on: ${{ matrix.settings.host }}
+ # if: github.ref_name == 'beta'
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: apple-actions/import-codesign-certs@v2
+ if: runner.os == 'macOS'
+ with:
+ keychain: build
+ p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+ p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+ - name: Setup Apple API Key
+ if: runner.os == 'macOS'
+ run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
+
+ - uses: ./.github/actions/setup-bun
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ - name: Cache apt packages
+ if: contains(matrix.settings.host, 'ubuntu')
+ uses: actions/cache@v4
+ with:
+ path: ~/apt-cache
+ key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
+
+ - name: Install dependencies (ubuntu only)
+ if: contains(matrix.settings.host, 'ubuntu')
+ run: |
+ mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
+ sudo chmod -R a+rw ~/apt-cache
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Prepare
+ run: bun ./scripts/prepare.ts
+ working-directory: packages/desktop-electron
+ env:
+ OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+ RUST_TARGET: ${{ matrix.settings.target }}
+ GH_TOKEN: ${{ github.token }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+
+ - name: Build
+ run: bun run build
+ working-directory: packages/desktop-electron
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+ - name: Package and publish
+ if: needs.version.outputs.release
+ run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
+ working-directory: packages/desktop-electron
+ timeout-minutes: 60
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+ GH_TOKEN: ${{ steps.committer.outputs.token }}
+ CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
+ CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
+ APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
+ APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
+
+ - name: Package (no publish)
+ if: ${{ !needs.version.outputs.release }}
+ run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
+ working-directory: packages/desktop-electron
+ timeout-minutes: 60
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: opencode-electron-${{ matrix.settings.target }}
+ path: packages/desktop-electron/dist/*
+
+ - uses: actions/upload-artifact@v4
+ if: needs.version.outputs.release
+ with:
+ name: latest-yml-${{ matrix.settings.target }}
+ path: packages/desktop-electron/dist/latest*.yml
+
publish:
needs:
- version
- build-cli
- build-tauri
+ - build-electron
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
@@ -281,6 +400,12 @@ jobs:
name: opencode-cli
path: packages/opencode/dist
+ - uses: actions/download-artifact@v4
+ if: needs.version.outputs.release
+ with:
+ pattern: latest-yml-*
+ path: /tmp/latest-yml
+
- name: Cache apt packages (AUR)
uses: actions/cache@v4
with:
@@ -308,3 +433,4 @@ jobs:
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
GH_REPO: ${{ needs.version.outputs.repo }}
NPM_CONFIG_PROVENANCE: false
+ LATEST_YML_DIR: /tmp/latest-yml
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
new file mode 100644
index 0000000000..6d143a8a22
--- /dev/null
+++ b/.github/workflows/storybook.yml
@@ -0,0 +1,38 @@
+name: storybook
+
+on:
+ push:
+ branches: [dev]
+ paths:
+ - ".github/workflows/storybook.yml"
+ - "package.json"
+ - "bun.lock"
+ - "packages/storybook/**"
+ - "packages/ui/**"
+ pull_request:
+ branches: [dev]
+ paths:
+ - ".github/workflows/storybook.yml"
+ - "package.json"
+ - "bun.lock"
+ - "packages/storybook/**"
+ - "packages/ui/**"
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: storybook build
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Build Storybook
+ run: bun --cwd packages/storybook build
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 647b9e1886..f7b00516f8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -8,8 +8,16 @@ on:
workflow_dispatch:
jobs:
unit:
- name: unit (linux)
- runs-on: blacksmith-4vcpu-ubuntu-2404
+ name: unit (${{ matrix.settings.name }})
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - name: linux
+ host: blacksmith-4vcpu-ubuntu-2404
+ - name: windows
+ host: blacksmith-4vcpu-windows-2025
+ runs-on: ${{ matrix.settings.host }}
defaults:
run:
shell: bash
diff --git a/.github/workflows/vouch-check-issue.yml b/.github/workflows/vouch-check-issue.yml
index 94569f4731..4c2aa960b2 100644
--- a/.github/workflows/vouch-check-issue.yml
+++ b/.github/workflows/vouch-check-issue.yml
@@ -42,15 +42,17 @@ jobs:
throw error;
}
- // Parse the .td file for denounced users
+ // Parse the .td file for vouched and denounced users
+ const vouched = new Set();
const denounced = new Map();
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
- if (!trimmed.startsWith('-')) continue;
- const rest = trimmed.slice(1).trim();
+ const isDenounced = trimmed.startsWith('-');
+ const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
if (!rest) continue;
+
const spaceIdx = rest.indexOf(' ');
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
@@ -65,32 +67,50 @@ jobs:
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
if (!username) continue;
- denounced.set(username.toLowerCase(), reason);
+ if (isDenounced) {
+ denounced.set(username.toLowerCase(), reason);
+ continue;
+ }
+
+ vouched.add(username.toLowerCase());
}
// Check if the author is denounced
const reason = denounced.get(author.toLowerCase());
- if (reason === undefined) {
- core.info(`User ${author} is not denounced. Allowing issue.`);
+ if (reason !== undefined) {
+ // Author is denounced — close the issue
+ const body = 'This issue has been automatically closed.';
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ body,
+ });
+
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ state: 'closed',
+ state_reason: 'not_planned',
+ });
+
+ core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
return;
}
- // Author is denounced — close the issue
- const body = 'This issue has been automatically closed.';
+ // Author is positively vouched — add label
+ if (!vouched.has(author.toLowerCase())) {
+ core.info(`User ${author} is not denounced or vouched. Allowing issue.`);
+ return;
+ }
- await github.rest.issues.createComment({
+ await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
- body,
+ labels: ['Vouched'],
});
- await github.rest.issues.update({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: issueNumber,
- state: 'closed',
- state_reason: 'not_planned',
- });
-
- core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
+ core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
diff --git a/.github/workflows/vouch-check-pr.yml b/.github/workflows/vouch-check-pr.yml
index 470b8e0a5a..51816dfb75 100644
--- a/.github/workflows/vouch-check-pr.yml
+++ b/.github/workflows/vouch-check-pr.yml
@@ -6,6 +6,7 @@ on:
permissions:
contents: read
+ issues: write
pull-requests: write
jobs:
@@ -42,15 +43,17 @@ jobs:
throw error;
}
- // Parse the .td file for denounced users
+ // Parse the .td file for vouched and denounced users
+ const vouched = new Set();
const denounced = new Map();
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
- if (!trimmed.startsWith('-')) continue;
- const rest = trimmed.slice(1).trim();
+ const isDenounced = trimmed.startsWith('-');
+ const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
if (!rest) continue;
+
const spaceIdx = rest.indexOf(' ');
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
@@ -65,29 +68,47 @@ jobs:
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
if (!username) continue;
- denounced.set(username.toLowerCase(), reason);
+ if (isDenounced) {
+ denounced.set(username.toLowerCase(), reason);
+ continue;
+ }
+
+ vouched.add(username.toLowerCase());
}
// Check if the author is denounced
const reason = denounced.get(author.toLowerCase());
- if (reason === undefined) {
- core.info(`User ${author} is not denounced. Allowing PR.`);
+ if (reason !== undefined) {
+ // Author is denounced — close the PR
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body: 'This pull request has been automatically closed.',
+ });
+
+ await github.rest.pulls.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: prNumber,
+ state: 'closed',
+ });
+
+ core.info(`Closed PR #${prNumber} from denounced user ${author}`);
return;
}
- // Author is denounced — close the PR
- await github.rest.issues.createComment({
+ // Author is positively vouched — add label
+ if (!vouched.has(author.toLowerCase())) {
+ core.info(`User ${author} is not denounced or vouched. Allowing PR.`);
+ return;
+ }
+
+ await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
- body: 'This pull request has been automatically closed.',
+ labels: ['Vouched'],
});
- await github.rest.pulls.update({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: prNumber,
- state: 'closed',
- });
-
- core.info(`Closed PR #${prNumber} from denounced user ${author}`);
+ core.info(`Added vouched label to PR #${prNumber} from ${author}`);
diff --git a/.github/workflows/vouch-manage-by-issue.yml b/.github/workflows/vouch-manage-by-issue.yml
index cf0524c21a..9604bf87f3 100644
--- a/.github/workflows/vouch-manage-by-issue.yml
+++ b/.github/workflows/vouch-manage-by-issue.yml
@@ -33,5 +33,6 @@ jobs:
with:
issue-id: ${{ github.event.issue.number }}
comment-id: ${{ github.event.comment.id }}
+ roles: admin,maintain
env:
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md
index 0c2e176d8b..263afbe9b5 100644
--- a/.opencode/agent/translator.md
+++ b/.opencode/agent/translator.md
@@ -1,7 +1,7 @@
---
description: Translate content for a specified locale while preserving technical terms
mode: subagent
-model: opencode/gemini-3.1-pro
+model: opencode/gemini-3-pro
---
You are a professional translator and localization specialist.
@@ -13,10 +13,25 @@ Requirements:
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
- Also preserve every term listed in the Do-Not-Translate glossary below.
+- Also apply locale-specific guidance from `.opencode/glossary/.md` when available (for example, `zh-cn.md`).
- Do not modify fenced code blocks.
- Output ONLY the translation (no commentary).
If the target locale is missing, ask the user to provide it.
+If no locale-specific glossary exists, use the global glossary only.
+
+---
+
+# Locale-Specific Glossaries
+
+When a locale glossary exists, use it to:
+
+- Apply preferred wording for recurring UI/docs terms in that locale
+- Preserve locale-specific do-not-translate terms and casing decisions
+- Prefer natural phrasing over literal translation when the locale file calls it out
+- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo)
+
+Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below.
---
diff --git a/.opencode/glossary/README.md b/.opencode/glossary/README.md
new file mode 100644
index 0000000000..983900381c
--- /dev/null
+++ b/.opencode/glossary/README.md
@@ -0,0 +1,63 @@
+# Locale Glossaries
+
+Use this folder for locale-specific translation guidance that supplements `.opencode/agent/translator.md`.
+
+The global glossary in `translator.md` remains the source of truth for shared do-not-translate terms (commands, code, paths, product names, etc.). These locale files capture community learnings about phrasing and terminology preferences.
+
+## File Naming
+
+- One file per locale
+- Use lowercase locale slugs that match docs locales when possible (for example, `zh-cn.md`, `zh-tw.md`)
+- If only language-level guidance exists, use the language code (for example, `fr.md`)
+- Some repo locale slugs may be aliases/non-BCP47 for consistency (for example, `br` for Brazilian Portuguese / `pt-BR`)
+
+## What To Put In A Locale File
+
+- **Sources**: PRs/issues/discussions that motivated the guidance
+- **Do Not Translate (Locale Additions)**: locale-specific terms or casing decisions
+- **Preferred Terms**: recurring UI/docs words with preferred translations
+- **Guidance**: tone, style, and consistency notes
+- **Avoid** (optional): common literal translations or wording we should avoid
+- If the repo uses a locale alias slug, document the alias in **Guidance** (for example, prose may mention `pt-BR` while config/examples use `br`)
+
+Prefer guidance that is:
+
+- Repeated across multiple docs/screens
+- Easy to apply consistently
+- Backed by a community contribution or review discussion
+
+## Template
+
+```md
+# Glossary
+
+## Sources
+
+- PR #12345: https://github.com/anomalyco/opencode/pull/12345
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing)
+
+## Preferred Terms
+
+| English | Preferred | Notes |
+| ------- | --------- | --------- |
+| prompt | ... | preferred |
+| session | ... | preferred |
+
+## Guidance
+
+- Prefer natural phrasing over literal translation
+
+## Avoid
+
+- Avoid ... when ...
+```
+
+## Contribution Notes
+
+- Mark entries as preferred when they may evolve
+- Keep examples short
+- Add or update the `Sources` section whenever you add a new rule
+- Prefer PR-backed guidance over invented term mappings; start with general guidance if no term-level corrections exist yet
diff --git a/.opencode/glossary/ar.md b/.opencode/glossary/ar.md
new file mode 100644
index 0000000000..37355522a0
--- /dev/null
+++ b/.opencode/glossary/ar.md
@@ -0,0 +1,28 @@
+# ar Glossary
+
+## Sources
+
+- PR #9947: https://github.com/anomalyco/opencode/pull/9947
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Arabic phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- For RTL text, treat code, commands, and paths as LTR artifacts and keep their character order unchanged
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Arabic terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/br.md b/.opencode/glossary/br.md
new file mode 100644
index 0000000000..fd3e7251cd
--- /dev/null
+++ b/.opencode/glossary/br.md
@@ -0,0 +1,34 @@
+# br Glossary
+
+## Sources
+
+- PR #10086: https://github.com/anomalyco/opencode/pull/10086
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Locale code `br` in repo config, code, and paths (repo alias for Brazilian Portuguese)
+
+## Preferred Terms
+
+These are PR-backed locale naming preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------- |
+| Brazilian Portuguese (prose locale name) | `pt-BR` | Use standard locale naming in prose when helpful |
+| Repo locale slug (code/config) | `br` | PR #10086 uses `br` for consistency/simplicity |
+| Browser locale detection | `pt`, `pt-br`, `pt-BR` -> `br` | Preserve this mapping in docs/examples about locale detection |
+
+## Guidance
+
+- This file covers Brazilian Portuguese (`pt-BR`), but the repo locale code is `br`
+- Use natural Brazilian Portuguese phrasing over literal translation
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- Keep repo locale identifiers as implemented in code/config (`br`) even when prose mentions `pt-BR`
+
+## Avoid
+
+- Avoid changing repo locale code references from `br` to `pt-br` in code snippets, paths, or config examples
+- Avoid mixing Portuguese variants when a Brazilian Portuguese form is established
diff --git a/.opencode/glossary/bs.md b/.opencode/glossary/bs.md
new file mode 100644
index 0000000000..aa3bd96f6f
--- /dev/null
+++ b/.opencode/glossary/bs.md
@@ -0,0 +1,33 @@
+# bs Glossary
+
+## Sources
+
+- PR #12283: https://github.com/anomalyco/opencode/pull/12283
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+These are PR-backed locale naming preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| ---------------------------------- | ---------- | ------------------------------------------------- |
+| Bosnian language label (UI) | `Bosanski` | PR #12283 tested switching language to `Bosanski` |
+| Repo locale slug (code/config) | `bs` | Preserve in code, config, paths, and examples |
+| Browser locale detection (Bosnian) | `bs` | PR #12283 added `bs` locale auto-detection |
+
+## Guidance
+
+- Use natural Bosnian phrasing over literal translation
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- Keep repo locale references as `bs` in code/config, and use `Bosanski` for the user-facing language name when applicable
+
+## Avoid
+
+- Avoid changing repo locale references from `bs` to another slug in code snippets or config examples
+- Avoid translating product and protocol names that are fixed identifiers
diff --git a/.opencode/glossary/da.md b/.opencode/glossary/da.md
new file mode 100644
index 0000000000..e632221701
--- /dev/null
+++ b/.opencode/glossary/da.md
@@ -0,0 +1,27 @@
+# da Glossary
+
+## Sources
+
+- PR #9821: https://github.com/anomalyco/opencode/pull/9821
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Danish phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Danish terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/de.md b/.opencode/glossary/de.md
new file mode 100644
index 0000000000..0d2c49face
--- /dev/null
+++ b/.opencode/glossary/de.md
@@ -0,0 +1,27 @@
+# de Glossary
+
+## Sources
+
+- PR #9817: https://github.com/anomalyco/opencode/pull/9817
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural German phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple German terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/es.md b/.opencode/glossary/es.md
new file mode 100644
index 0000000000..dc9b977ecf
--- /dev/null
+++ b/.opencode/glossary/es.md
@@ -0,0 +1,27 @@
+# es Glossary
+
+## Sources
+
+- PR #9817: https://github.com/anomalyco/opencode/pull/9817
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Spanish phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Spanish terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/fr.md b/.opencode/glossary/fr.md
new file mode 100644
index 0000000000..074c4de110
--- /dev/null
+++ b/.opencode/glossary/fr.md
@@ -0,0 +1,27 @@
+# fr Glossary
+
+## Sources
+
+- PR #9821: https://github.com/anomalyco/opencode/pull/9821
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural French phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple French terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/ja.md b/.opencode/glossary/ja.md
new file mode 100644
index 0000000000..f0159ca966
--- /dev/null
+++ b/.opencode/glossary/ja.md
@@ -0,0 +1,33 @@
+# ja Glossary
+
+## Sources
+
+- PR #9821: https://github.com/anomalyco/opencode/pull/9821
+- PR #13160: https://github.com/anomalyco/opencode/pull/13160
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+These are PR-backed wording preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| --------------------------- | ----------------------- | ------------------------------------- |
+| WSL integration (UI label) | `WSL連携` | PR #13160 prefers this over `WSL統合` |
+| WSL integration description | `WindowsのWSL環境で...` | PR #13160 improved phrasing naturally |
+
+## Guidance
+
+- Prefer natural Japanese phrasing over literal translation
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- In WSL integration text, follow PR #13160 wording direction for more natural Japanese phrasing
+
+## Avoid
+
+- Avoid `WSL統合` in the WSL integration UI context where `WSL連携` is the reviewed wording
+- Avoid translating product and protocol names that are fixed identifiers
diff --git a/.opencode/glossary/ko.md b/.opencode/glossary/ko.md
new file mode 100644
index 0000000000..71385c8a10
--- /dev/null
+++ b/.opencode/glossary/ko.md
@@ -0,0 +1,27 @@
+# ko Glossary
+
+## Sources
+
+- PR #9817: https://github.com/anomalyco/opencode/pull/9817
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Korean phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Korean terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/no.md b/.opencode/glossary/no.md
new file mode 100644
index 0000000000..d7159dca41
--- /dev/null
+++ b/.opencode/glossary/no.md
@@ -0,0 +1,38 @@
+# no Glossary
+
+## Sources
+
+- PR #10018: https://github.com/anomalyco/opencode/pull/10018
+- PR #12935: https://github.com/anomalyco/opencode/pull/12935
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Sound names (PR #10018 notes these were intentionally left untranslated)
+
+## Preferred Terms
+
+These are PR-backed corrections and may evolve.
+
+| English / Context | Preferred | Notes |
+| ----------------------------------- | ------------ | ----------------------------- |
+| Save (data persistence action) | `Lagre` | Prefer over `Spare` |
+| Disabled (feature/state) | `deaktivert` | Prefer over `funksjonshemmet` |
+| API keys | `API Nøkler` | Prefer over `API Taster` |
+| Cost (noun) | `Kostnad` | Prefer over verb form `Koste` |
+| Show/View (imperative button label) | `Vis` | Prefer over `Utsikt` |
+
+## Guidance
+
+- Prefer natural Norwegian Bokmal (Bokmål) wording over literal translation
+- Keep tone clear and practical in UI labels
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- Keep recurring UI terms consistent once a preferred term is chosen
+
+## Avoid
+
+- Avoid `Spare` for save actions in persistence contexts
+- Avoid `funksjonshemmet` for disabled feature states
+- Avoid `API Taster`, `Koste`, and `Utsikt` in the corrected contexts above
diff --git a/.opencode/glossary/pl.md b/.opencode/glossary/pl.md
new file mode 100644
index 0000000000..e9bad7a515
--- /dev/null
+++ b/.opencode/glossary/pl.md
@@ -0,0 +1,27 @@
+# pl Glossary
+
+## Sources
+
+- PR #9884: https://github.com/anomalyco/opencode/pull/9884
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Polish phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Polish terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/ru.md b/.opencode/glossary/ru.md
new file mode 100644
index 0000000000..6fee0f94c0
--- /dev/null
+++ b/.opencode/glossary/ru.md
@@ -0,0 +1,27 @@
+# ru Glossary
+
+## Sources
+
+- PR #9882: https://github.com/anomalyco/opencode/pull/9882
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
+
+## Guidance
+
+- Prefer natural Russian phrasing over literal translation
+- Keep tone clear and direct in UI labels and docs prose
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+
+## Avoid
+
+- Avoid translating product and protocol names that are fixed identifiers
+- Avoid mixing multiple Russian terms for the same recurring UI action once a preferred term is established
diff --git a/.opencode/glossary/th.md b/.opencode/glossary/th.md
new file mode 100644
index 0000000000..7b5a31d16b
--- /dev/null
+++ b/.opencode/glossary/th.md
@@ -0,0 +1,34 @@
+# th Glossary
+
+## Sources
+
+- PR #10809: https://github.com/anomalyco/opencode/pull/10809
+- PR #11496: https://github.com/anomalyco/opencode/pull/11496
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+These are PR-backed preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
+| Thai language label in language lists | `ไทย` | PR #10809 standardized this across locales |
+| Language names in language pickers | Native names (static) | PR #11496: keep names like `English`, `Deutsch`, `ไทย` consistent across locales |
+
+## Guidance
+
+- Prefer natural Thai phrasing over literal translation
+- Keep tone short and clear for buttons and labels
+- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
+- Keep language names static/native in language pickers instead of translating them per current locale (PR #11496)
+
+## Avoid
+
+- Avoid translating language names differently per current locale in language lists
+- Avoid changing `ไทย` to another display form for the Thai language option unless the product standard changes
diff --git a/.opencode/glossary/tr.md b/.opencode/glossary/tr.md
new file mode 100644
index 0000000000..72b1cdfb40
--- /dev/null
+++ b/.opencode/glossary/tr.md
@@ -0,0 +1,38 @@
+# tr Glossary
+
+## Sources
+
+- PR #15835: https://github.com/anomalyco/opencode/pull/15835
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose, docs, and UI copy)
+- Keep lowercase `opencode` in commands, package names, paths, URLs, and other exact identifiers
+- `` stays the literal key token in code blocks; use `Tab` for the nearby explanatory label in prose
+- Commands, flags, file paths, and code literals (keep exactly as written)
+
+## Preferred Terms
+
+These are PR-backed wording preferences and may evolve.
+
+| English / Context | Preferred | Notes |
+| ------------------------- | --------------------------------------- | ------------------------------------------------------------- |
+| available in beta | `beta olarak mevcut` | Prefer this over `beta olarak kullanılabilir` |
+| privacy-first | `Gizlilik öncelikli tasarlandı` | Prefer this over `Önce gizlilik için tasarlandı` |
+| connect your local models | `yerel modellerinizi bağlayabilirsiniz` | Use the fuller, more direct action phrase |
+| `` key label | `Tab` | Use `Tab` in prose; keep `` in literal UI or code blocks |
+| cross-platform | `cross-platform (tüm platformlarda)` | Keep the English term, add a short clarification when helpful |
+
+## Guidance
+
+- Prefer natural Turkish phrasing over literal translation
+- Merge broken sentence fragments into one clear sentence when the source is a single thought
+- Keep product naming consistent: `OpenCode` in prose, `opencode` only for exact technical identifiers
+- When an English technical term is intentionally kept, add a short Turkish clarification only if it improves readability
+
+## Avoid
+
+- Avoid `beta olarak kullanılabilir` when `beta olarak mevcut` fits
+- Avoid `Önce gizlilik için tasarlandı`; use the more natural reviewed wording instead
+- Avoid `Sekme` for the translated key label in prose when referring to ``
+- Avoid changing `opencode` to `OpenCode` inside commands, URLs, package names, or code literals
diff --git a/.opencode/glossary/zh-cn.md b/.opencode/glossary/zh-cn.md
new file mode 100644
index 0000000000..054e94b7e8
--- /dev/null
+++ b/.opencode/glossary/zh-cn.md
@@ -0,0 +1,42 @@
+# zh-cn Glossary
+
+## Sources
+
+- PR #13942: https://github.com/anomalyco/opencode/pull/13942
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
+- `OpenCode Zen`
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
+
+## Preferred Terms
+
+These are preferred terms for docs/UI prose and may evolve.
+
+| English | Preferred | Notes |
+| ----------------------- | --------- | ------------------------------------------- |
+| prompt | 提示词 | Keep `--prompt` unchanged in flags/code |
+| session | 会话 | |
+| provider | 提供商 | |
+| share link / shared URL | 分享链接 | Prefer `分享` for user-facing share actions |
+| headless (server) | 无界面 | Docs wording |
+| authentication | 认证 | Prefer in auth/OAuth contexts |
+| cache | 缓存 | |
+| keybind / shortcut | 快捷键 | User-facing docs wording |
+| workflow | 工作流 | e.g. GitHub Actions workflow |
+
+## Guidance
+
+- Prefer natural, concise phrasing over literal translation
+- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
+- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
+- Keep enum-like values in English when they are literals (for example, `default`, `json`)
+- Prefer consistent terminology across pages once a term is chosen (`会话`, `提供商`, `提示词`, etc.)
+
+## Avoid
+
+- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
+- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
diff --git a/.opencode/glossary/zh-tw.md b/.opencode/glossary/zh-tw.md
new file mode 100644
index 0000000000..283660e121
--- /dev/null
+++ b/.opencode/glossary/zh-tw.md
@@ -0,0 +1,42 @@
+# zh-tw Glossary
+
+## Sources
+
+- PR #13942: https://github.com/anomalyco/opencode/pull/13942
+
+## Do Not Translate (Locale Additions)
+
+- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
+- `OpenCode Zen`
+- `OpenCode CLI`
+- `CLI`, `TUI`, `MCP`, `OAuth`
+- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
+
+## Preferred Terms
+
+These are preferred terms for docs/UI prose and may evolve.
+
+| English | Preferred | Notes |
+| ----------------------- | --------- | ------------------------------------------- |
+| prompt | 提示詞 | Keep `--prompt` unchanged in flags/code |
+| session | 工作階段 | |
+| provider | 供應商 | |
+| share link / shared URL | 分享連結 | Prefer `分享` for user-facing share actions |
+| headless (server) | 無介面 | Docs wording |
+| authentication | 認證 | Prefer in auth/OAuth contexts |
+| cache | 快取 | |
+| keybind / shortcut | 快捷鍵 | User-facing docs wording |
+| workflow | 工作流程 | e.g. GitHub Actions workflow |
+
+## Guidance
+
+- Prefer natural, concise phrasing over literal translation
+- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
+- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
+- Keep enum-like values in English when they are literals (for example, `default`, `json`)
+- Prefer consistent terminology across pages once a term is chosen (`工作階段`, `供應商`, `提示詞`, etc.)
+
+## Avoid
+
+- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
+- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc
index 3497847a67..8380f7f719 100644
--- a/.opencode/opencode.jsonc
+++ b/.opencode/opencode.jsonc
@@ -5,6 +5,11 @@
"options": {},
},
},
+ "permission": {
+ "edit": {
+ "packages/opencode/migration/*": "deny",
+ },
+ },
"mcp": {},
"tools": {
"github-triage": false,
diff --git a/.opencode/tool/github-pr-search.txt b/.opencode/tool/github-pr-search.txt
index 28d8643f13..1b658e71c4 100644
--- a/.opencode/tool/github-pr-search.txt
+++ b/.opencode/tool/github-pr-search.txt
@@ -1,6 +1,6 @@
Use this tool to search GitHub pull requests by title and description.
-This tool searches PRs in the sst/opencode repository and returns LLM-friendly results including:
+This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including:
- PR number and title
- Author
- State (open/closed/merged)
diff --git a/AGENTS.md b/AGENTS.md
index 758714d10a..2158d73af1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -20,6 +20,17 @@
Prefer single word names for variables and functions. Only use multiple words if necessary.
+### Naming Enforcement (Read This)
+
+THIS RULE IS MANDATORY FOR AGENT WRITTEN CODE.
+
+- Use single word names by default for new locals, params, and helper functions.
+- Multi-word names are allowed only when a single word would be unclear or ambiguous.
+- Do not introduce new camelCase compounds when a short single-word alternative is clear.
+- Before finishing edits, review touched lines and shorten newly introduced identifiers where possible.
+- Good short names to prefer: `pid`, `cfg`, `err`, `opts`, `dir`, `root`, `child`, `state`, `timeout`.
+- Examples to avoid unless truly required: `inputPID`, `existingClient`, `connectTimeout`, `workerPath`.
+
```ts
// Good
const foo = 1
diff --git a/README.ar.md b/README.ar.md
index f24e598d5e..beb44589e6 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.bn.md b/README.bn.md
new file mode 100644
index 0000000000..c7abc7346a
--- /dev/null
+++ b/README.bn.md
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+ওপেন সোর্স এআই কোডিং এজেন্ট।
+
+
+
+
+
+
+
+ English |
+ 简体中文 |
+ 繁體中文 |
+ 한국어 |
+ Deutsch |
+ Español |
+ Français |
+ Italiano |
+ Dansk |
+ 日本語 |
+ Polski |
+ Русский |
+ Bosanski |
+ العربية |
+ Norsk |
+ Português (Brasil) |
+ ไทย |
+ Türkçe |
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
+
+
+[](https://opencode.ai)
+
+---
+
+### ইনস্টলেশন (Installation)
+
+```bash
+# YOLO
+curl -fsSL https://opencode.ai/install | bash
+
+# Package managers
+npm i -g opencode-ai@latest # or bun/pnpm/yarn
+scoop install opencode # Windows
+choco install opencode # Windows
+brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
+brew install opencode # macOS and Linux (official brew formula, updated less)
+sudo pacman -S opencode # Arch Linux (Stable)
+paru -S opencode-bin # Arch Linux (Latest from AUR)
+mise use -g opencode # Any OS
+nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
+```
+
+> [!TIP]
+> ইনস্টল করার আগে ০.১.x এর চেয়ে পুরোনো ভার্সনগুলো মুছে ফেলুন।
+
+### ডেস্কটপ অ্যাপ (BETA)
+
+OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
+
+| প্ল্যাটফর্ম | ডাউনলোড |
+| --------------------- | ------------------------------------- |
+| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
+| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
+| Windows | `opencode-desktop-windows-x64.exe` |
+| Linux | `.deb`, `.rpm`, or AppImage |
+
+```bash
+# macOS (Homebrew)
+brew install --cask opencode-desktop
+# Windows (Scoop)
+scoop bucket add extras; scoop install extras/opencode-desktop
+```
+
+#### ইনস্টলেশন ডিরেক্টরি (Installation Directory)
+
+ইনস্টল স্ক্রিপ্টটি ইনস্টলেশন পাতের জন্য নিম্নলিখিত অগ্রাধিকার ক্রম মেনে চলে:
+
+1. `$OPENCODE_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি
+2. `$XDG_BIN_DIR` - XDG বেস ডিরেক্টরি স্পেসিফিকেশন সমর্থিত পাথ
+3. `$HOME/bin` - সাধারণ ব্যবহারকারী বাইনারি ডিরেক্টরি (যদি বিদ্যমান থাকে বা তৈরি করা যায়)
+4. `$HOME/.opencode/bin` - ডিফল্ট ফলব্যাক
+
+```bash
+# উদাহরণ
+OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
+XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
+```
+
+### এজেন্টস (Agents)
+
+OpenCode এ দুটি বিল্ট-ইন এজেন্ট রয়েছে যা আপনি `Tab` কি(key) দিয়ে পরিবর্তন করতে পারবেন।
+
+- **build** - ডিফল্ট, ডেভেলপমেন্টের কাজের জন্য সম্পূর্ণ অ্যাক্সেসযুক্ত এজেন্ট
+- **plan** - বিশ্লেষণ এবং কোড এক্সপ্লোরেশনের জন্য রিড-ওনলি এজেন্ট
+ - ডিফল্টভাবে ফাইল এডিট করতে দেয় না
+ - ব্যাশ কমান্ড চালানোর আগে অনুমতি চায়
+ - অপরিচিত কোডবেস এক্সপ্লোর করা বা পরিবর্তনের পরিকল্পনা করার জন্য আদর্শ
+
+এছাড়াও জটিল অনুসন্ধান এবং মাল্টিস্টেপ টাস্কের জন্য একটি **general** সাবএজেন্ট অন্তর্ভুক্ত রয়েছে।
+এটি অভ্যন্তরীণভাবে ব্যবহৃত হয় এবং মেসেজে `@general` লিখে ব্যবহার করা যেতে পারে।
+
+এজেন্টদের সম্পর্কে আরও জানুন: [docs](https://opencode.ai/docs/agents)।
+
+### ডকুমেন্টেশন (Documentation)
+
+কিভাবে OpenCode কনফিগার করবেন সে সম্পর্কে আরও তথ্যের জন্য, [**আমাদের ডকস দেখুন**](https://opencode.ai/docs)।
+
+### অবদান (Contributing)
+
+আপনি যদি OpenCode এ অবদান রাখতে চান, অনুগ্রহ করে একটি পুল রিকোয়েস্ট সাবমিট করার আগে আমাদের [কন্ট্রিবিউটিং ডকস](./CONTRIBUTING.md) পড়ে নিন।
+
+### OpenCode এর উপর বিল্ডিং (Building on OpenCode)
+
+আপনি যদি এমন প্রজেক্টে কাজ করেন যা OpenCode এর সাথে সম্পর্কিত এবং প্রজেক্টের নামের অংশ হিসেবে "opencode" ব্যবহার করেন, উদাহরণস্বরূপ "opencode-dashboard" বা "opencode-mobile", তবে দয়া করে আপনার README তে একটি নোট যোগ করে স্পষ্ট করুন যে এই প্রজেক্টটি OpenCode দল দ্বারা তৈরি হয়নি এবং আমাদের সাথে এর কোনো সরাসরি সম্পর্ক নেই।
+
+### সচরাচর জিজ্ঞাসিত প্রশ্নাবলী (FAQ)
+
+#### এটি ক্লড কোড (Claude Code) থেকে কীভাবে আলাদা?
+
+ক্যাপাবিলিটির দিক থেকে এটি ক্লড কোডের (Claude Code) মতই। এখানে মূল পার্থক্যগুলো দেওয়া হলো:
+
+- ১০০% ওপেন সোর্স
+- কোনো প্রোভাইডারের সাথে আবদ্ধ নয়। যদিও আমরা [OpenCode Zen](https://opencode.ai/zen) এর মাধ্যমে মডেলসমূহ ব্যবহারের পরামর্শ দিই, OpenCode ক্লড (Claude), ওপেনএআই (OpenAI), গুগল (Google), অথবা লোকাল মডেলগুলোর সাথেও ব্যবহার করা যেতে পারে। যেমন যেমন মডেলগুলো উন্নত হবে, তাদের মধ্যকার পার্থক্য কমে আসবে এবং দামও কমবে, তাই প্রোভাইডার-অজ্ঞাস্টিক হওয়া খুবই গুরুত্বপূর্ণ।
+- আউট-অফ-দ্য-বক্স LSP সাপোর্ট
+- TUI এর উপর ফোকাস। OpenCode নিওভিম (neovim) ব্যবহারকারী এবং [terminal.shop](https://terminal.shop) এর নির্মাতাদের দ্বারা তৈরি; আমরা টার্মিনালে কী কী সম্ভব তার সীমাবদ্ধতা ছাড়িয়ে যাওয়ার চেষ্টা করছি।
+- ক্লায়েন্ট/সার্ভার আর্কিটেকচার। এটি যেমন OpenCode কে আপনার কম্পিউটারে চালানোর সুযোগ দেয়, তেমনি আপনি মোবাইল অ্যাপ থেকে রিমোটলি এটি নিয়ন্ত্রণ করতে পারবেন, অর্থাৎ TUI ফ্রন্টএন্ড কেবল সম্ভাব্য ক্লায়েন্টগুলোর মধ্যে একটি।
+
+---
+
+**আমাদের কমিউনিটিতে যুক্ত হোন** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
diff --git a/README.br.md b/README.br.md
index 4802c4996f..6d1de21562 100644
--- a/README.br.md
+++ b/README.br.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.bs.md b/README.bs.md
index 9ad6852018..2cff8e0279 100644
--- a/README.bs.md
+++ b/README.bs.md
@@ -33,7 +33,10 @@
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.da.md b/README.da.md
index 4b1302dbc3..ac522f29c4 100644
--- a/README.da.md
+++ b/README.da.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.de.md b/README.de.md
index 16116dc72f..87a670f3fc 100644
--- a/README.de.md
+++ b/README.de.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.es.md b/README.es.md
index 5c18ff4aca..9e456af1c0 100644
--- a/README.es.md
+++ b/README.es.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.fr.md b/README.fr.md
index 0382164bed..c1fca23376 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.gr.md b/README.gr.md
new file mode 100644
index 0000000000..2b2c2679d8
--- /dev/null
+++ b/README.gr.md
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+Ο πράκτορας τεχνητής νοημοσύνης ανοικτού κώδικα για προγραμματισμό.
+
+
+
+
+
+
+
+ English |
+ 简体中文 |
+ 繁體中文 |
+ 한국어 |
+ Deutsch |
+ Español |
+ Français |
+ Italiano |
+ Dansk |
+ 日本語 |
+ Polski |
+ Русский |
+ Bosanski |
+ العربية |
+ Norsk |
+ Português (Brasil) |
+ ไทย |
+ Türkçe |
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
+
+
+[](https://opencode.ai)
+
+---
+
+### Εγκατάσταση
+
+```bash
+# YOLO
+curl -fsSL https://opencode.ai/install | bash
+
+# Διαχειριστές πακέτων
+npm i -g opencode-ai@latest # ή bun/pnpm/yarn
+scoop install opencode # Windows
+choco install opencode # Windows
+brew install anomalyco/tap/opencode # macOS και Linux (προτείνεται, πάντα ενημερωμένο)
+brew install opencode # macOS και Linux (επίσημος τύπος brew, λιγότερο συχνές ενημερώσεις)
+sudo pacman -S opencode # Arch Linux (Σταθερό)
+paru -S opencode-bin # Arch Linux (Τελευταία έκδοση από AUR)
+mise use -g opencode # Οποιοδήποτε λειτουργικό σύστημα
+nix run nixpkgs#opencode # ή github:anomalyco/opencode με βάση την πιο πρόσφατη αλλαγή από το dev branch
+```
+
+> [!TIP]
+> Αφαίρεσε παλαιότερες εκδόσεις από τη 0.1.x πριν από την εγκατάσταση.
+
+### Εφαρμογή Desktop (BETA)
+
+Το OpenCode είναι επίσης διαθέσιμο ως εφαρμογή. Κατέβασε το απευθείας από τη [σελίδα εκδόσεων](https://github.com/anomalyco/opencode/releases) ή το [opencode.ai/download](https://opencode.ai/download).
+
+| Πλατφόρμα | Λήψη |
+| --------------------- | ------------------------------------- |
+| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
+| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
+| Windows | `opencode-desktop-windows-x64.exe` |
+| Linux | `.deb`, `.rpm`, ή AppImage |
+
+```bash
+# macOS (Homebrew)
+brew install --cask opencode-desktop
+# Windows (Scoop)
+scoop bucket add extras; scoop install extras/opencode-desktop
+```
+
+#### Κατάλογος Εγκατάστασης
+
+Το script εγκατάστασης τηρεί την ακόλουθη σειρά προτεραιότητας για τη διαδρομή εγκατάστασης:
+
+1. `$OPENCODE_INSTALL_DIR` - Προσαρμοσμένος κατάλογος εγκατάστασης
+2. `$XDG_BIN_DIR` - Διαδρομή συμβατή με τις προδιαγραφές XDG Base Directory
+3. `$HOME/bin` - Τυπικός κατάλογος εκτελέσιμων αρχείων χρήστη (εάν υπάρχει ή μπορεί να δημιουργηθεί)
+4. `$HOME/.opencode/bin` - Προεπιλεγμένη εφεδρική διαδρομή
+
+```bash
+# Παραδείγματα
+OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
+XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
+```
+
+### Πράκτορες
+
+Το OpenCode περιλαμβάνει δύο ενσωματωμένους πράκτορες μεταξύ των οποίων μπορείτε να εναλλάσσεστε με το πλήκτρο `Tab`.
+
+- **build** - Προεπιλεγμένος πράκτορας με πλήρη πρόσβαση για εργασία πάνω σε κώδικα
+- **plan** - Πράκτορας μόνο ανάγνωσης για ανάλυση και εξερεύνηση κώδικα
+ - Αρνείται την επεξεργασία αρχείων από προεπιλογή
+ - Ζητά άδεια πριν εκτελέσει εντολές bash
+ - Ιδανικός για εξερεύνηση άγνωστων αρχείων πηγαίου κώδικα ή σχεδιασμό αλλαγών
+
+Περιλαμβάνεται επίσης ένας **general** υποπράκτορας για σύνθετες αναζητήσεις και πολυβηματικές διεργασίες.
+Χρησιμοποιείται εσωτερικά και μπορεί να κληθεί χρησιμοποιώντας `@general` στα μηνύματα.
+
+Μάθετε περισσότερα για τους [πράκτορες](https://opencode.ai/docs/agents).
+
+### Οδηγός Χρήσης
+
+Για περισσότερες πληροφορίες σχετικά με τη ρύθμιση του OpenCode, [**πλοηγήσου στον οδηγό χρήσης μας**](https://opencode.ai/docs).
+
+### Συνεισφορά
+
+Εάν ενδιαφέρεσαι να συνεισφέρεις στο OpenCode, διαβάστε τα [οδηγό χρήσης συνεισφοράς](./CONTRIBUTING.md) πριν υποβάλεις ένα pull request.
+
+### Δημιουργία πάνω στο OpenCode
+
+Εάν εργάζεσαι σε ένα έργο σχετικό με το OpenCode και χρησιμοποιείτε το "opencode" ως μέρος του ονόματός του, για παράδειγμα "opencode-dashboard" ή "opencode-mobile", πρόσθεσε μια σημείωση στο README σας για να διευκρινίσεις ότι δεν είναι κατασκευασμένο από την ομάδα του OpenCode και δεν έχει καμία σχέση με εμάς.
+
+### Συχνές Ερωτήσεις
+
+#### Πώς διαφέρει αυτό από το Claude Code;
+
+Είναι πολύ παρόμοιο με το Claude Code ως προς τις δυνατότητες. Ακολουθούν οι βασικές διαφορές:
+
+- 100% ανοιχτού κώδικα
+- Δεν είναι συνδεδεμένο με κανέναν πάροχο. Αν και συνιστούμε τα μοντέλα που παρέχουμε μέσω του [OpenCode Zen](https://opencode.ai/zen), το OpenCode μπορεί να χρησιμοποιηθεί με Claude, OpenAI, Google, ή ακόμα και τοπικά μοντέλα. Καθώς τα μοντέλα εξελίσσονται, τα κενά μεταξύ τους θα κλείσουν και οι τιμές θα μειωθούν, οπότε είναι σημαντικό να είσαι ανεξάρτητος από τον πάροχο.
+- Out-of-the-box υποστήριξη LSP
+- Εστίαση στο TUI. Το OpenCode είναι κατασκευασμένο από χρήστες που χρησιμοποιούν neovim και τους δημιουργούς του [terminal.shop](https://terminal.shop)· θα εξαντλήσουμε τα όρια του τι είναι δυνατό στο terminal.
+- Αρχιτεκτονική client/server. Αυτό, για παράδειγμα, μπορεί να επιτρέψει στο OpenCode να τρέχει στον υπολογιστή σου ενώ το χειρίζεσαι εξ αποστάσεως από μια εφαρμογή κινητού, που σημαίνει ότι το TUI frontend είναι μόνο ένας από τους πιθανούς clients.
+
+---
+
+**Γίνε μέλος της κοινότητάς μας** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
diff --git a/README.it.md b/README.it.md
index c966ccec49..3e516a9027 100644
--- a/README.it.md
+++ b/README.it.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.ja.md b/README.ja.md
index 11109e7eb4..144dc7b6f8 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.ko.md b/README.ko.md
index 23fea76b1e..32defc0a5e 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.md b/README.md
index 99b4b2c50f..79ccf8b349 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,10 @@
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.no.md b/README.no.md
index 9b9e90dc38..c3348286b2 100644
--- a/README.no.md
+++ b/README.no.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.pl.md b/README.pl.md
index fced98dfc3..4c5a076656 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.ru.md b/README.ru.md
index a7c590c16b..e507be70e6 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.th.md b/README.th.md
index 0999167f23..4a4ea62c95 100644
--- a/README.th.md
+++ b/README.th.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.tr.md b/README.tr.md
index 67f84e4ddb..e88b40f875 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.uk.md b/README.uk.md
index 77e859a45d..a1a0259b6d 100644
--- a/README.uk.md
+++ b/README.uk.md
@@ -33,7 +33,10 @@
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.vi.md b/README.vi.md
new file mode 100644
index 0000000000..0932c50f78
--- /dev/null
+++ b/README.vi.md
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+Trợ lý lập trình AI mã nguồn mở.
+
+
+
+
+
+
+
+ English |
+ 简体中文 |
+ 繁體中文 |
+ 한국어 |
+ Deutsch |
+ Español |
+ Français |
+ Italiano |
+ Dansk |
+ 日本語 |
+ Polski |
+ Русский |
+ Bosanski |
+ العربية |
+ Norsk |
+ Português (Brasil) |
+ ไทย |
+ Türkçe |
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
+
+
+[](https://opencode.ai)
+
+---
+
+### Cài đặt
+
+```bash
+# YOLO
+curl -fsSL https://opencode.ai/install | bash
+
+# Các trình quản lý gói (Package managers)
+npm i -g opencode-ai@latest # hoặc bun/pnpm/yarn
+scoop install opencode # Windows
+choco install opencode # Windows
+brew install anomalyco/tap/opencode # macOS và Linux (khuyên dùng, luôn cập nhật)
+brew install opencode # macOS và Linux (công thức brew chính thức, ít cập nhật hơn)
+sudo pacman -S opencode # Arch Linux (Bản ổn định)
+paru -S opencode-bin # Arch Linux (Bản mới nhất từ AUR)
+mise use -g opencode # Mọi hệ điều hành
+nix run nixpkgs#opencode # hoặc github:anomalyco/opencode cho nhánh dev mới nhất
+```
+
+> [!TIP]
+> Hãy xóa các phiên bản cũ hơn 0.1.x trước khi cài đặt.
+
+### Ứng dụng Desktop (BETA)
+
+OpenCode cũng có sẵn dưới dạng ứng dụng desktop. Tải trực tiếp từ [trang releases](https://github.com/anomalyco/opencode/releases) hoặc [opencode.ai/download](https://opencode.ai/download).
+
+| Nền tảng | Tải xuống |
+| --------------------- | ------------------------------------- |
+| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
+| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
+| Windows | `opencode-desktop-windows-x64.exe` |
+| Linux | `.deb`, `.rpm`, hoặc AppImage |
+
+```bash
+# macOS (Homebrew)
+brew install --cask opencode-desktop
+# Windows (Scoop)
+scoop bucket add extras; scoop install extras/opencode-desktop
+```
+
+#### Thư mục cài đặt
+
+Tập lệnh cài đặt tuân theo thứ tự ưu tiên sau cho đường dẫn cài đặt:
+
+1. `$OPENCODE_INSTALL_DIR` - Thư mục cài đặt tùy chỉnh
+2. `$XDG_BIN_DIR` - Đường dẫn tuân thủ XDG Base Directory Specification
+3. `$HOME/bin` - Thư mục nhị phân tiêu chuẩn của người dùng (nếu tồn tại hoặc có thể tạo)
+4. `$HOME/.opencode/bin` - Mặc định dự phòng
+
+```bash
+# Ví dụ
+OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
+XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
+```
+
+### Agents (Đại diện)
+
+OpenCode bao gồm hai agent được tích hợp sẵn mà bạn có thể chuyển đổi bằng phím `Tab`.
+
+- **build** - Agent mặc định, có toàn quyền truy cập cho công việc lập trình
+- **plan** - Agent chỉ đọc dùng để phân tích và khám phá mã nguồn
+ - Mặc định từ chối việc chỉnh sửa tệp
+ - Hỏi quyền trước khi chạy các lệnh bash
+ - Lý tưởng để khám phá các codebase lạ hoặc lên kế hoạch thay đổi
+
+Ngoài ra còn có một subagent **general** dùng cho các tìm kiếm phức tạp và tác vụ nhiều bước.
+Agent này được sử dụng nội bộ và có thể gọi bằng cách dùng `@general` trong tin nhắn.
+
+Tìm hiểu thêm về [agents](https://opencode.ai/docs/agents).
+
+### Tài liệu
+
+Để biết thêm thông tin về cách cấu hình OpenCode, [**hãy truy cập tài liệu của chúng tôi**](https://opencode.ai/docs).
+
+### Đóng góp
+
+Nếu bạn muốn đóng góp cho OpenCode, vui lòng đọc [tài liệu hướng dẫn đóng góp](./CONTRIBUTING.md) trước khi gửi pull request.
+
+### Xây dựng trên nền tảng OpenCode
+
+Nếu bạn đang làm việc trên một dự án liên quan đến OpenCode và sử dụng "opencode" như một phần của tên dự án, ví dụ "opencode-dashboard" hoặc "opencode-mobile", vui lòng thêm một ghi chú vào README của bạn để làm rõ rằng dự án đó không được xây dựng bởi đội ngũ OpenCode và không liên kết với chúng tôi dưới bất kỳ hình thức nào.
+
+### Các câu hỏi thường gặp (FAQ)
+
+#### OpenCode khác biệt thế nào so với Claude Code?
+
+Về mặt tính năng, nó rất giống Claude Code. Dưới đây là những điểm khác biệt chính:
+
+- 100% mã nguồn mở
+- Không bị ràng buộc với bất kỳ nhà cung cấp nào. Mặc dù chúng tôi khuyên dùng các mô hình được cung cấp qua [OpenCode Zen](https://opencode.ai/zen), OpenCode có thể được sử dụng với Claude, OpenAI, Google, hoặc thậm chí các mô hình chạy cục bộ. Khi các mô hình phát triển, khoảng cách giữa chúng sẽ thu hẹp lại và giá cả sẽ giảm, vì vậy việc không phụ thuộc vào nhà cung cấp là rất quan trọng.
+- Hỗ trợ LSP ngay từ đầu
+- Tập trung vào TUI (Giao diện người dùng dòng lệnh). OpenCode được xây dựng bởi những người dùng neovim và đội ngũ tạo ra [terminal.shop](https://terminal.shop); chúng tôi sẽ đẩy giới hạn của những gì có thể làm được trên terminal lên mức tối đa.
+- Kiến trúc client/server. Chẳng hạn, điều này cho phép OpenCode chạy trên máy tính của bạn trong khi bạn điều khiển nó từ xa qua một ứng dụng di động, nghĩa là frontend TUI chỉ là một trong những client có thể dùng.
+
+---
+
+**Tham gia cộng đồng của chúng tôi** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
diff --git a/README.zh.md b/README.zh.md
index 113d476b2e..b11d9857c9 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/README.zht.md b/README.zht.md
index b518104443..573ca85ab4 100644
--- a/README.zht.md
+++ b/README.zht.md
@@ -27,12 +27,16 @@
日本語 |
Polski |
Русский |
+ Bosanski |
العربية |
Norsk |
Português (Brasil) |
ไทย |
Türkçe |
- Українська
+ Українська |
+ বাংলা |
+ Ελληνικά |
+ Tiếng Việt
[](https://opencode.ai)
diff --git a/bun.lock b/bun.lock
index 04da112cf7..4b7097232a 100644
--- a/bun.lock
+++ b/bun.lock
@@ -15,17 +15,18 @@
"@actions/artifact": "5.0.1",
"@tsconfig/bun": "catalog:",
"@types/mime-types": "3.0.1",
+ "@typescript/native-preview": "catalog:",
"glob": "13.0.5",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",
- "turbo": "2.5.6",
+ "turbo": "2.8.13",
},
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -46,7 +47,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
- "ghostty-web": "0.4.0",
+ "ghostty-web": "github:anomalyco/ghostty-web#main",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
@@ -75,7 +76,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -109,7 +110,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -136,7 +137,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -160,7 +161,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -184,7 +185,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -215,9 +216,39 @@
"vite": "catalog:",
},
},
+ "packages/desktop-electron": {
+ "name": "@opencode-ai/desktop-electron",
+ "version": "1.2.24",
+ "dependencies": {
+ "@opencode-ai/app": "workspace:*",
+ "@opencode-ai/ui": "workspace:*",
+ "@solid-primitives/i18n": "2.2.1",
+ "@solid-primitives/storage": "catalog:",
+ "@solidjs/meta": "catalog:",
+ "@solidjs/router": "0.15.4",
+ "electron-log": "^5",
+ "electron-store": "^10",
+ "electron-updater": "^6",
+ "electron-window-state": "^5.0.3",
+ "marked": "^15",
+ "solid-js": "catalog:",
+ "tree-kill": "^1.2.2",
+ },
+ "devDependencies": {
+ "@actions/artifact": "4.0.0",
+ "@types/bun": "catalog:",
+ "@types/node": "catalog:",
+ "@typescript/native-preview": "catalog:",
+ "electron": "40.4.1",
+ "electron-builder": "^26",
+ "electron-vite": "^5",
+ "typescript": "~5.6.2",
+ "vite": "catalog:",
+ },
+ },
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -246,7 +277,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -262,7 +293,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.2.10",
+ "version": "1.2.24",
"bin": {
"opencode": "./bin/opencode",
},
@@ -304,8 +335,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
- "@opentui/core": "0.1.79",
- "@opentui/solid": "0.1.79",
+ "@opentui/core": "0.1.86",
+ "@opentui/solid": "0.1.86",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -320,7 +351,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
- "drizzle-orm": "1.0.0-beta.12-a5629fb",
+ "drizzle-orm": "1.0.0-beta.16-ea816b6",
"fuzzysort": "3.1.0",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
@@ -335,6 +366,7 @@
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",
+ "semver": "^7.6.3",
"solid-js": "catalog:",
"strip-ansi": "7.1.2",
"tree-sitter-bash": "0.25.0",
@@ -342,6 +374,7 @@
"ulid": "catalog:",
"vscode-jsonrpc": "8.2.1",
"web-tree-sitter": "0.25.10",
+ "which": "6.0.1",
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
@@ -363,11 +396,13 @@
"@types/babel__core": "7.20.5",
"@types/bun": "catalog:",
"@types/mime-types": "3.0.1",
+ "@types/semver": "^7.5.8",
"@types/turndown": "5.0.5",
+ "@types/which": "3.0.4",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
- "drizzle-kit": "1.0.0-beta.12-a5629fb",
- "drizzle-orm": "1.0.0-beta.12-a5629fb",
+ "drizzle-kit": "1.0.0-beta.16-ea816b6",
+ "drizzle-orm": "1.0.0-beta.16-ea816b6",
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
@@ -376,7 +411,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -390,13 +425,17 @@
},
"packages/script": {
"name": "@opencode-ai/script",
+ "dependencies": {
+ "semver": "^7.6.3",
+ },
"devDependencies": {
"@types/bun": "catalog:",
+ "@types/semver": "^7.5.8",
},
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.2.10",
+ "version": "1.2.24",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -407,7 +446,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -418,9 +457,31 @@
"typescript": "catalog:",
},
},
+ "packages/storybook": {
+ "name": "@opencode-ai/storybook",
+ "devDependencies": {
+ "@opencode-ai/ui": "workspace:*",
+ "@solidjs/meta": "catalog:",
+ "@storybook/addon-a11y": "^10.2.13",
+ "@storybook/addon-docs": "^10.2.13",
+ "@storybook/addon-links": "^10.2.13",
+ "@storybook/addon-onboarding": "^10.2.13",
+ "@storybook/addon-vitest": "^10.2.13",
+ "@tailwindcss/vite": "catalog:",
+ "@tsconfig/node22": "catalog:",
+ "@types/node": "catalog:",
+ "@types/react": "18.0.25",
+ "react": "18.2.0",
+ "solid-js": "catalog:",
+ "storybook": "^10.2.13",
+ "storybook-solidjs-vite": "^10.0.9",
+ "typescript": "catalog:",
+ "vite": "catalog:",
+ },
+ },
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -431,7 +492,7 @@
"@solid-primitives/media": "2.3.3",
"@solid-primitives/resize-observer": "2.1.3",
"@solidjs/meta": "catalog:",
- "@typescript/native-preview": "catalog:",
+ "@solidjs/router": "catalog:",
"dompurify": "3.3.1",
"fuzzysort": "catalog:",
"katex": "0.16.27",
@@ -440,6 +501,9 @@
"marked-katex-extension": "5.1.6",
"marked-shiki": "catalog:",
"morphdom": "2.7.8",
+ "motion": "12.34.5",
+ "motion-dom": "12.34.3",
+ "motion-utils": "12.29.2",
"remeda": "catalog:",
"shiki": "catalog:",
"solid-js": "catalog:",
@@ -453,6 +517,7 @@
"@types/bun": "catalog:",
"@types/katex": "0.16.7",
"@types/luxon": "catalog:",
+ "@typescript/native-preview": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
@@ -462,7 +527,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"zod": "catalog:",
},
@@ -473,7 +538,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.2.10",
+ "version": "1.2.24",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -506,6 +571,7 @@
},
},
"trustedDependencies": [
+ "electron",
"esbuild",
"web-tree-sitter",
"tree-sitter-bash",
@@ -524,7 +590,7 @@
"@kobalte/core": "0.13.11",
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
- "@pierre/diffs": "1.1.0-beta.13",
+ "@pierre/diffs": "1.1.0-beta.18",
"@playwright/test": "1.51.0",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4",
@@ -541,8 +607,8 @@
"ai": "5.0.124",
"diff": "8.0.2",
"dompurify": "3.3.1",
- "drizzle-kit": "1.0.0-beta.12-a5629fb",
- "drizzle-orm": "1.0.0-beta.12-a5629fb",
+ "drizzle-kit": "1.0.0-beta.16-ea816b6",
+ "drizzle-orm": "1.0.0-beta.16-ea816b6",
"fuzzysort": "3.1.0",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
@@ -562,6 +628,8 @@
"zod": "4.1.8",
},
"packages": {
+ "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="],
+
"@actions/artifact": ["@actions/artifact@5.0.1", "", { "dependencies": { "@actions/core": "^2.0.0", "@actions/github": "^6.0.1", "@actions/http-client": "^3.0.0", "@azure/storage-blob": "^12.29.1", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-dHJ5rHduhCKUikKTT9eXeWoUvfKia3IjR1sO/VTAV3DVAL4yMTRnl2iO5mcfiBjySHLwPNezwENAVskKYU5ymw=="],
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
@@ -834,6 +902,8 @@
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
+ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
+
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
@@ -886,12 +956,30 @@
"@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="],
+ "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="],
+
"@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
"@dot/log": ["@dot/log@0.1.5", "", { "dependencies": { "chalk": "^4.1.2", "loglevelnext": "^6.0.0", "p-defer": "^3.0.0" } }, "sha512-ECraEVJWv2f2mWK93lYiefUkphStVlKD6yKDzisuoEmxuLKrxO9iGetHK2DoEAkj7sxjE886n0OUVVCUx0YPNg=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
+ "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
+
+ "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="],
+
+ "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],
+
+ "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="],
+
+ "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="],
+
+ "@electron/rebuild": ["@electron/rebuild@4.0.3", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA=="],
+
+ "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="],
+
+ "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="],
+
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
@@ -1004,6 +1092,8 @@
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
+ "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
+
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="],
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="],
@@ -1136,6 +1226,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=="],
@@ -1206,8 +1298,14 @@
"@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
+ "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="],
+
+ "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="],
+
"@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=="],
@@ -1232,6 +1330,12 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+ "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="],
+
+ "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="],
+
+ "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="],
+
"@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="],
"@octokit/auth-oauth-app": ["@octokit/auth-oauth-app@9.0.3", "", { "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/auth-oauth-user": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg=="],
@@ -1290,6 +1394,8 @@
"@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"],
+ "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"],
+
"@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"],
"@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"],
@@ -1302,6 +1408,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"],
@@ -1314,21 +1422,21 @@
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
- "@opentui/core": ["@opentui/core@0.1.79", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.79", "@opentui/core-darwin-x64": "0.1.79", "@opentui/core-linux-arm64": "0.1.79", "@opentui/core-linux-x64": "0.1.79", "@opentui/core-win32-arm64": "0.1.79", "@opentui/core-win32-x64": "0.1.79", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-job/t09w8A/aHb/WuaVbimu5fIffyN+PCuVO5cYhXEg/NkOkC/WdFi80B8bwncR/DBPyLAh6oJ3EG86grOVo5g=="],
+ "@opentui/core": ["@opentui/core@0.1.86", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.86", "@opentui/core-darwin-x64": "0.1.86", "@opentui/core-linux-arm64": "0.1.86", "@opentui/core-linux-x64": "0.1.86", "@opentui/core-win32-arm64": "0.1.86", "@opentui/core-win32-x64": "0.1.86", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-3tRLbI9ADrQE1jEEn4x2aJexEOQZkv9Emk2BixMZqxfVhz2zr2SxtpimDAX0vmZK3+GnWAwBWxuaCAsxZpY4+w=="],
- "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.79", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kgsGniV+DM5G1P3GideyJhvfnthNKcVCAm2mPTIr9InQ3L0gS/Feh7zgwOS/jxDvdlQbOWGKMk2Z3JApeC1MLw=="],
+ "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.86", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Zp7q64+d+Dcx6YrH3mRcnHq8EOBnrfc1RvjgSWLhpXr49hY6LzuhqpfZM57aGErPYlR+ff8QM6e5FUkFnDfyjw=="],
- "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.79", "", { "os": "darwin", "cpu": "x64" }, "sha512-OpyAmFqAAKQ2CeFmf/oLWcNksmP6Ryx/3R5dbKXThOudMCeQvfvInJTRbc2jTn9VFpf+Qj4BgHkJg1h90tf/EA=="],
+ "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.86", "", { "os": "darwin", "cpu": "x64" }, "sha512-NcxfjCJm1kLnTMVOpAPdRYNi8W8XdAXNa6N7i9khiVFrl2v5KRQfUjbrSOUYVxFJNc3jKFG6rsn3jEApvn92qA=="],
- "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.79", "", { "os": "linux", "cpu": "arm64" }, "sha512-DCa5YaknS4bWhFt8TMEGH+qmTinyzuY8hoZbO4crtWXAxofPP7Pas76Cwxlvis/PyLffA+pPxAl1l5sUZpsvqw=="],
+ "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.86", "", { "os": "linux", "cpu": "arm64" }, "sha512-EDHAvqSOr8CXzbDvo1aE5blJ6wu1aSbR2LqoXtoeXHemr2T2W42D2TdIWewG6K+/BuRbzZnqt9wnYFBksLW6lw=="],
- "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.79", "", { "os": "linux", "cpu": "x64" }, "sha512-V6xjvFfHh3NGvsuuDae1KHPRZXHMEE8XL0A/GM6v4I4OCC23kDmkK60Vn6OptQwAzwwbz0X0IX+Ut/GQU9qGgA=="],
+ "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.86", "", { "os": "linux", "cpu": "x64" }, "sha512-VBaBkVdQDxYV4WcKjb+jgyMS5PiVHepvfaoKWpz1Bq+J01xXW4XPcXyPGkgR1+2R93KzaugEnLscTW4mWtLHlQ=="],
- "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.79", "", { "os": "win32", "cpu": "arm64" }, "sha512-sPRKnVzOdT5szI59tte7pxwwkYA+07EQN+6miFAvkFuiLmRUngONUD8HVjL7nCnxcPFqxaU4Rvl1y40ST86g8g=="],
+ "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.86", "", { "os": "win32", "cpu": "arm64" }, "sha512-xKbT7sEKYKGwUPkoqmLfHjbJU+vwHPDwf/r/mIunL41JXQBB35CSZ3/QgIwpp2kkteu7oE1tdBdg15ogUU4OMg=="],
- "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.79", "", { "os": "win32", "cpu": "x64" }, "sha512-vmQcFTvKf9fqajnDtgU6/uAsiTGwx8//khqHVBmiTEXUsiT792Ki9l8sgNughbuldqG5iZOiF6IaAWU1H67UpA=="],
+ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.86", "", { "os": "win32", "cpu": "x64" }, "sha512-HRfgAUlcu71/MrtgfX4Gj7PsDtfXZiuC506Pkn1OnRN1Xomcu10BVRDweUa0/g8ldU9i9kLjMGGnpw6/NjaBFg=="],
- "@opentui/solid": ["@opentui/solid@0.1.79", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.79", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-c5+0jexKxb8GwRDDkQ/U6isZZqClAzHccXmYiLYmSnqdoQQp2lIGHLartL+K8lfIQrsKClzP2ZHumN6nexRfRg=="],
+ "@opentui/solid": ["@opentui/solid@0.1.86", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.86", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-pOZC9dlZIH+bpstVVZ2AvYukBnslZTKSl/y5H8FWcMTHGv/BzpGxXBxstL65E/IQASqPFbvFcs7yMRzdLhynmA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1442,7 +1550,9 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
- "@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
+ "@pierre/diffs": ["@pierre/diffs@1.1.0-beta.18", "", { "dependencies": { "@pierre/theme": "0.0.22", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-7ZF3YD9fxdbYsPnltz5cUqHacN7ztp8RX/fJLxwv8wIEORpP4+7dHz1h/qx3o4EW2xUrIhmbM8ImywLasB787Q=="],
+
+ "@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
@@ -1602,7 +1712,7 @@
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
- "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
+ "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
"@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="],
@@ -1774,10 +1884,32 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+ "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-zuR1n1xgWoieEnr6E5xdTR40BI61IBQahgmsRpTvqRffL3mxAs5aFoORDmA5pZWI2LE9URdMkY85h218ijuLiw=="],
+
+ "@storybook/addon-docs": ["@storybook/addon-docs@10.2.13", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.13", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.13", "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.13" } }, "sha512-puMxpJbt/CuodLIbKDxWrW1ZgADYomfNHWEKp2d2l2eJjp17rADx0h3PABuNbX+YHbJwYcDdqluSnQwMysFEOA=="],
+
+ "@storybook/addon-links": ["@storybook/addon-links@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" }, "optionalPeers": ["react"] }, "sha512-8wnAomGiHaUpNIc+lOzmazTrebxa64z9rihIbM/Q59vkOImHQNkGp7KP/qNgJA4GPTFtu8+fLjX2qCoAQPM0jQ=="],
+
+ "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.13", "", { "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-kw2GgIY67UR8YXKfuVS0k+mfWL1joNQHeSe5DlDL4+7qbgp9zfV6cRJ199BMdfRAQNMzQoxHgRUcAMAqs3Rkpw=="],
+
+ "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.13", "", { "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.13", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-qQD3xzxc31cQHS0loF9enGWi5sgA6zBTbaJ0HuSUNGO81iwfLSALh8L/1vrD5NfN2vlBeUMTsgv3EkCuLfe9EQ=="],
+
+ "@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.13", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.13", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-gUCR7PmyrWYj3dIJJgxOm25dcXFolPIUPmug3z90Aaon7YPXw3pUN+dNDx8KqDJqRK1WDIB4HaefgYZIm5V7iA=="],
+
+ "@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.13", "", { "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.13" } }, "sha512-ZSduoB10qTI0V9z22qeULmQLsvTs8d/rtJi03qbVxpPiMRor86AmyAaBrfhGGmWBxWQZpOGQQm6yIT2YLoPs7w=="],
+
"@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=="],
+ "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
+
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
@@ -1866,16 +1998,26 @@
"@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=="],
+ "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="],
+
"@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="],
"@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="],
"@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=="],
@@ -1890,6 +2032,8 @@
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
+ "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
+
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
@@ -1908,8 +2052,12 @@
"@types/fontkit": ["@types/fontkit@2.0.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew=="],
+ "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="],
+
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+ "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="],
+
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/is-stream": ["@types/is-stream@1.1.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg=="],
@@ -1922,6 +2070,8 @@
"@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="],
+ "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
+
"@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
@@ -1944,6 +2094,8 @@
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
+ "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="],
+
"@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="],
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
@@ -1956,12 +2108,16 @@
"@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="],
+ "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
+
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
+ "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
+
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
@@ -1976,14 +2132,20 @@
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+ "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="],
+
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
+ "@types/which": ["@types/which@3.0.4", "", {}, "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w=="],
+
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
+ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
+
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251207.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251207.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4QcRnzB0pi9rS0AOvg8kWbmuwHv5X7B2EXHbgcms9+56hsZ8SZrZjNgBJb2rUIodJ4kU5mrkj/xlTTT4r9VcpQ=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251207.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-waWJnuuvkXh4WdpbTjYf7pyahJzx0ycesV2BylyHrE9OxU9FSKcD/cRLQYvbq3YcBSdF7sZwRLDBer7qTeLsYA=="],
@@ -2010,7 +2172,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 +2182,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=="],
@@ -2042,6 +2204,8 @@
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
+ "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
+
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
"abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="],
@@ -2062,6 +2226,8 @@
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
+ "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
+
"ai": ["ai@5.0.124", "", { "dependencies": { "@ai-sdk/gateway": "2.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Li6Jw9F9qsvFJXZPBfxj38ddP2iURCnMs96f9Q3OeQzrDVcl1hvtwSEAuxA/qmfh6SDV2ERqFUOFzigvr0697g=="],
"ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="],
@@ -2072,6 +2238,8 @@
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
+ "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="],
+
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
@@ -2088,12 +2256,20 @@
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+ "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="],
+
+ "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="],
+
+ "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="],
+
"archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="],
"archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="],
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
+ "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="],
+
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -2114,8 +2290,14 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
+ "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
+
"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=="],
+
+ "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
+
"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=="],
@@ -2124,12 +2306,18 @@
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
+ "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="],
+
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+ "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
+
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
+ "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="],
+
"autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
@@ -2144,6 +2332,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=="],
@@ -2182,6 +2372,8 @@
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
+ "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
+
"bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="],
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
@@ -2196,6 +2388,8 @@
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+ "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
+
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
@@ -2220,13 +2414,17 @@
"buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
+ "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="],
+
+ "builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="],
+
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
- "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
+ "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="],
@@ -2242,6 +2440,14 @@
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
+ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
+
+ "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="],
+
+ "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="],
+
+ "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="],
+
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -2258,7 +2464,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 +2480,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=="],
@@ -2282,6 +2490,8 @@
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
+ "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="],
+
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
@@ -2290,16 +2500,24 @@
"clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="],
+ "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
+
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
+
"cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
+ "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="],
+
"clipboardy": ["clipboardy@4.0.0", "", { "dependencies": { "execa": "^8.0.1", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w=="],
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
+ "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
+
"cloudflare": ["cloudflare@5.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -2324,16 +2542,24 @@
"common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="],
+ "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="],
+
"compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="],
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
"condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="],
+ "conf": ["conf@14.0.0", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "atomically": "^2.0.3", "debounce-fn": "^6.0.0", "dot-prop": "^9.0.0", "env-paths": "^3.0.0", "json-schema-typed": "^8.0.1", "semver": "^7.7.2", "uint8array-extras": "^1.4.0" } }, "sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw=="],
+
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
"config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+ "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="],
+
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -2346,14 +2572,18 @@
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
- "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
+ "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
+ "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="],
+
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
"crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="],
+ "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="],
+
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -2368,6 +2598,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=="],
@@ -2382,18 +2614,30 @@
"db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="],
+ "debounce-fn": ["debounce-fn@6.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ=="],
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
+ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
+
+ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+
+ "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
+
"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=="],
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
+
+ "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="],
+
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
@@ -2404,6 +2648,8 @@
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
+ "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="],
+
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
@@ -2418,6 +2664,8 @@
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
+ "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
+
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="],
@@ -2432,14 +2680,22 @@
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
+ "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="],
+
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
+ "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="],
+
+ "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="],
+
"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=="],
@@ -2454,11 +2710,13 @@
"dot-prop": ["dot-prop@8.0.2", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="],
- "dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
+ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
- "drizzle-kit": ["drizzle-kit@1.0.0-beta.12-a5629fb", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l+p4QOMvPGYBYEE9NBlU7diu+NSlxuOUwi0I7i01Uj1PpfU0NxhPzaks/9q1MDw4FAPP8vdD0dOhoqosKtRWWQ=="],
+ "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="],
- "drizzle-orm": ["drizzle-orm@1.0.0-beta.12-a5629fb", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-wyOAgr9Cy9oEN6z5S0JGhfipLKbRRJtQKgbDO9SXGR9swMBbGNIlXkeMqPRrqYQ8k70mh+7ZJ/eVmJ2F7zR3Vg=="],
+ "drizzle-kit": ["drizzle-kit@1.0.0-beta.16-ea816b6", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GiJQqCNPZP8Kk+i7/sFa3rtXbq26tLDNi3LbMx9aoLuwF2ofk8CS7cySUGdI+r4J3q0a568quC8FZeaFTCw4IA=="],
+
+ "drizzle-orm": ["drizzle-orm@1.0.0-beta.16-ea816b6", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-k9gT4f0O9Qvah5YK/zL+FZonQ8TPyVxcG/ojN4dzO0fHP8hs8tBno8lqmJo53g0JLWv3Q2nsTUoyBRKM2TljFw=="],
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
@@ -2472,8 +2730,30 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
+
+ "electron": ["electron@40.4.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-N1ZXybQZL8kYemO8vAeh9nrk4mSvqlAO8xs0QCHkXIvRnuB/7VGwEehjvQbsU5/f4bmTKpG+2GQERe/zmKpudQ=="],
+
+ "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="],
+
+ "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="],
+
+ "electron-log": ["electron-log@5.4.3", "", {}, "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ=="],
+
+ "electron-publish": ["electron-publish@26.8.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w=="],
+
+ "electron-store": ["electron-store@10.1.0", "", { "dependencies": { "conf": "^14.0.0", "type-fest": "^4.41.0" } }, "sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w=="],
+
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
+ "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="],
+
+ "electron-vite": ["electron-vite@5.0.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-arrow-functions": "^7.27.1", "cac": "^6.7.14", "esbuild": "^0.25.11", "magic-string": "^0.30.19", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ=="],
+
+ "electron-window-state": ["electron-window-state@5.0.3", "", { "dependencies": { "jsonfile": "^4.0.0", "mkdirp": "^0.5.1" } }, "sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg=="],
+
+ "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="],
+
"emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="],
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
@@ -2482,6 +2762,10 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+ "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
+
+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
@@ -2490,6 +2774,10 @@
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
+ "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
+
+ "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
+
"error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="],
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
@@ -2512,6 +2800,8 @@
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
+ "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
+
"esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
@@ -2562,8 +2852,12 @@
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
+ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
+
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
+ "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
+
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
@@ -2576,6 +2870,10 @@
"extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
+ "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
+
+ "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],
+
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
@@ -2586,6 +2884,8 @@
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
"fast-json-stringify": ["fast-json-stringify@6.3.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="],
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
@@ -2600,12 +2900,18 @@
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
+ "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
+ "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
+
+ "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
@@ -2646,8 +2952,12 @@
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
+
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
+ "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
+
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -2660,6 +2970,8 @@
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
+ "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="],
+
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
@@ -2682,18 +2994,20 @@
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
- "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
+ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
- "ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
+ "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="],
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
+ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
+
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
@@ -2702,6 +3016,8 @@
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
+ "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="],
+
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
"globby": ["globby@11.0.4", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg=="],
@@ -2712,6 +3028,8 @@
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+ "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="],
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
@@ -2738,6 +3056,8 @@
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+ "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="],
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="],
@@ -2790,6 +3110,8 @@
"hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="],
+ "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
+
"html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
@@ -2812,6 +3134,8 @@
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+ "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="],
+
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
@@ -2822,6 +3146,8 @@
"i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
+ "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="],
+
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"ieee754": ["ieee754@1.1.13", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="],
@@ -2834,6 +3160,14 @@
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+
+ "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="],
+
+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
@@ -2842,6 +3176,8 @@
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
+ "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
+
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -2898,6 +3234,10 @@
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
+ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
+
+ "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
+
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
@@ -2926,6 +3266,8 @@
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
+ "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
+
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
@@ -2942,7 +3284,9 @@
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
- "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="],
+
+ "isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
@@ -2952,6 +3296,8 @@
"jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
+ "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
+
"jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -2980,6 +3326,8 @@
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
+ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="],
@@ -2990,11 +3338,13 @@
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
+ "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
- "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+ "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
@@ -3006,6 +3356,8 @@
"katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="],
+ "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
@@ -3016,6 +3368,8 @@
"language-map": ["language-map@1.5.0", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="],
+ "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="],
+
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
@@ -3052,10 +3406,14 @@
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
+ "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
+
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
+ "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="],
+
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
@@ -3066,6 +3424,8 @@
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
+ "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
+
"loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
@@ -3074,8 +3434,12 @@
"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=="],
+ "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
+
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="],
@@ -3084,10 +3448,14 @@
"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=="],
+ "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="],
+
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
@@ -3098,6 +3466,8 @@
"marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="],
+ "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"md-to-react-email": ["md-to-react-email@5.0.0", "", { "dependencies": { "marked": "7.0.4" }, "peerDependencies": { "react": "18.x" } }, "sha512-GdBrBUbAAJHypnuyofYGfVos8oUslxHx69hs3CW9P0L8mS1sT6GnJuMBTlz/Fw+2widiwdavcu9UwyLF/BzZ4w=="],
@@ -3234,6 +3604,12 @@
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
+ "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
+
+ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
+
+ "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=="],
@@ -3242,12 +3618,30 @@
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+ "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="],
+
+ "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="],
+
+ "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="],
+
+ "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="],
+
+ "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
+
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
+ "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
+
"morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="],
+ "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="],
+
+ "motion-dom": ["motion-dom@12.34.3", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="],
+
+ "motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
+
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -3270,6 +3664,8 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
+
"native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
@@ -3284,14 +3680,20 @@
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
+ "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
+
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
+ "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="],
+
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
+ "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="],
+
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"node-html-parser": ["node-html-parser@7.0.2", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ=="],
@@ -3304,8 +3706,12 @@
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+ "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="],
+
"npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="],
+ "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="],
+
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="],
@@ -3356,12 +3762,16 @@
"opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="],
+ "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
+
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="],
"oxc-transform": ["oxc-transform@0.96.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm64": "0.96.0", "@oxc-transform/binding-darwin-arm64": "0.96.0", "@oxc-transform/binding-darwin-x64": "0.96.0", "@oxc-transform/binding-freebsd-x64": "0.96.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.96.0", "@oxc-transform/binding-linux-arm64-gnu": "0.96.0", "@oxc-transform/binding-linux-arm64-musl": "0.96.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.96.0", "@oxc-transform/binding-linux-s390x-gnu": "0.96.0", "@oxc-transform/binding-linux-x64-gnu": "0.96.0", "@oxc-transform/binding-linux-x64-musl": "0.96.0", "@oxc-transform/binding-wasm32-wasi": "0.96.0", "@oxc-transform/binding-win32-arm64-msvc": "0.96.0", "@oxc-transform/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dQPNIF+gHpSkmC0+Vg9IktNyhcn28Y8R3eTLyzn52UNymkasLicl3sFAtz7oEVuFmCpgGjaUTKkwk+jW2cHpDQ=="],
+ "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
+
"p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="],
"p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
@@ -3370,6 +3780,8 @@
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+ "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
+
"p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="],
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
@@ -3414,6 +3826,8 @@
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
@@ -3426,10 +3840,16 @@
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
+
+ "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="],
+
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
+ "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
+
"perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
@@ -3464,6 +3884,8 @@
"playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
+ "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
+
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
@@ -3486,24 +3908,40 @@
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
+ "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="],
+
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
+ "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
+
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"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=="],
+ "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
+
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
+ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
+
+ "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
+
+ "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
+
"promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
+ "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
+
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
@@ -3512,6 +3950,8 @@
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+ "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
+
"punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
@@ -3524,18 +3964,26 @@
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
+ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
+
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
+ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
+
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
"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=="],
@@ -3548,6 +3996,8 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.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-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
+ "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="],
+
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
@@ -3560,6 +4010,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 +4020,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=="],
@@ -3618,16 +4072,24 @@
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="],
+
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
+ "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
+
"resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+ "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
+
+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
+
"restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="],
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
@@ -3640,7 +4102,7 @@
"retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="],
- "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
@@ -3648,6 +4110,8 @@
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
+ "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
+
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -3674,6 +4138,8 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+ "sanitize-filename": ["sanitize-filename@1.6.3", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg=="],
+
"sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
@@ -3686,16 +4152,22 @@
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+ "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
+
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
+ "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
+
"seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="],
"seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="],
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
+ "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
+
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
@@ -3728,8 +4200,14 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+ "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
+
+ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
+
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
+ "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
+
"simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
@@ -3738,12 +4216,20 @@
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
+
+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
+
"smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
"socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
+ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
+
+ "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="],
+
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
@@ -3760,7 +4246,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=="],
@@ -3772,10 +4258,14 @@
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
+ "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="],
+
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
"srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="],
+ "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="],
+
"sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.18.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3MwIpMZhhdZKDqLp9ZQNlwkWix5+q+N0PWstuTomYwgZOxCCe6u9IIsoIszSk+GAJJN/jvGZyLiXKeV4iiQvw=="],
@@ -3800,6 +4290,8 @@
"stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="],
+ "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="],
+
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -3808,6 +4300,10 @@
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
+ "storybook": ["storybook@10.2.13", "", { "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-heMfJjOfbHvL+wlCAwFZlSxcakyJ5yQDam6e9k2RRArB1veJhRnsjO6lO1hOXjJYrqxfHA/ldIugbBVlCDqfvQ=="],
+
+ "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,18 +4330,28 @@
"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=="],
+
+ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
+
"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=="],
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
+ "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
+
+ "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
+
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
+ "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
+
"superstruct": ["superstruct@1.0.4", "", {}, "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -3860,12 +4366,18 @@
"tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="],
+ "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
+
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
"tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="],
"tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="],
+ "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="],
+
+ "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="],
+
"terracotta": ["terracotta@1.1.0", "", { "dependencies": { "solid-use": "^0.9.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww=="],
"terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
@@ -3882,10 +4394,14 @@
"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],
+ "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="],
+
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
+ "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="],
+
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
@@ -3896,8 +4412,14 @@
"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=="],
+ "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
+
+ "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="],
+
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
@@ -3912,14 +4434,20 @@
"traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
+ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
"tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+ "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="],
+
"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=="],
@@ -3932,19 +4460,21 @@
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
- "turbo": ["turbo@2.5.6", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.6", "turbo-darwin-arm64": "2.5.6", "turbo-linux-64": "2.5.6", "turbo-linux-arm64": "2.5.6", "turbo-windows-64": "2.5.6", "turbo-windows-arm64": "2.5.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w=="],
+ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
- "turbo-darwin-64": ["turbo-darwin-64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A=="],
+ "turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="],
- "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LyiG+rD7JhMfYwLqB6k3LZQtYn8CQQUePbpA8mF/hMLPAekXdJo1g0bUPw8RZLwQXUIU/3BU7tXENvhSGz5DPA=="],
+ "turbo-darwin-64": ["turbo-darwin-64@2.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="],
- "turbo-linux-64": ["turbo-linux-64@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-GOcUTT0xiT/pSnHL4YD6Yr3HreUhU8pUcGqcI2ksIF9b2/r/kRHwGFcsHgpG3+vtZF/kwsP0MV8FTlTObxsYIA=="],
+ "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ=="],
- "turbo-linux-arm64": ["turbo-linux-arm64@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-10Tm15bruJEA3m0V7iZcnQBpObGBcOgUcO+sY7/2vk1bweW34LMhkWi8svjV9iDF68+KJDThnYDlYE/bc7/zzQ=="],
+ "turbo-linux-64": ["turbo-linux-64@2.8.13", "", { "os": "linux", "cpu": "x64" }, "sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw=="],
- "turbo-windows-64": ["turbo-windows-64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-FyRsVpgaj76It0ludwZsNN40ytHN+17E4PFJyeliBEbxrGTc5BexlXVpufB7XlAaoaZVxbS6KT8RofLfDRyEPg=="],
+ "turbo-linux-arm64": ["turbo-linux-arm64@2.8.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg=="],
- "turbo-windows-arm64": ["turbo-windows-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-j/tWu8cMeQ7HPpKri6jvKtyXg9K1gRyhdK4tKrrchH8GNHscPX/F71zax58yYtLRWTiK04zNzPcUJuoS0+v/+Q=="],
+ "turbo-windows-64": ["turbo-windows-64@2.8.13", "", { "os": "win32", "cpu": "x64" }, "sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA=="],
+
+ "turbo-windows-arm64": ["turbo-windows-arm64@2.8.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA=="],
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
@@ -3970,6 +4500,8 @@
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
+
"ulid": ["ulid@3.0.1", "", { "bin": { "ulid": "dist/cli.js" } }, "sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q=="],
"ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
@@ -3992,6 +4524,10 @@
"unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="],
+ "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="],
+
+ "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="],
+
"unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="],
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
@@ -4020,18 +4556,26 @@
"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=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "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-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"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=="],
+
+ "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
+
"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=="],
@@ -4044,6 +4588,8 @@
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+ "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
+
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
@@ -4098,6 +4644,8 @@
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
+
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
@@ -4106,11 +4654,15 @@
"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=="],
- "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="],
+
+ "which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -4124,6 +4676,8 @@
"why-is-node-running": ["why-is-node-running@3.2.2", "", { "bin": { "why-is-node-running": "cli.js" } }, "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A=="],
+ "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="],
+
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"workerd": ["workerd@1.20251118.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="],
@@ -4164,6 +4718,8 @@
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+ "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
+
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
"yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="],
@@ -4260,6 +4816,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=="],
@@ -4432,8 +4990,42 @@
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
+ "@develar/schema-utils/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
+
"@dot/log/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+ "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
+
+ "@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "@electron/fuses/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
+
+ "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
+
+ "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
+
+ "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="],
+
+ "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "@electron/rebuild/node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="],
+
+ "@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="],
+
+ "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+ "@electron/universal/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="],
+
+ "@electron/universal/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "@electron/windows-sign/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="],
+
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
"@gitlab/gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="],
@@ -4488,6 +5080,10 @@
"@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=="],
+ "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
+
+ "@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=="],
@@ -4496,6 +5092,14 @@
"@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
+ "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "@npmcli/agent/socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
+
+ "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
"@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
"@octokit/auth-app/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
@@ -4560,6 +5164,12 @@
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
+ "@opencode-ai/desktop-electron/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
+
+ "@opencode-ai/desktop-electron/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
+
+ "@opencode-ai/desktop-electron/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
+
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
"@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
@@ -4572,6 +5182,8 @@
"@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
+ "@poppinss/dumper/@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
+
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="],
@@ -4612,6 +5224,8 @@
"@solidjs/start/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=="],
+ "@storybook/builder-vite/@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=="],
+
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
@@ -4632,8 +5246,20 @@
"@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/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
+
"@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=="],
@@ -4652,14 +5278,26 @@
"ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="],
+ "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
+
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="],
+
+ "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
+
+ "app-builder-lib/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
+
+ "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
+
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+ "are-we-there-yet/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
"astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
"astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
@@ -4688,16 +5326,58 @@
"buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "builder-util-runtime/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
+
"bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
"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=="],
+ "c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
+
+ "cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
+
+ "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
+
+ "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
+ "cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+
+ "cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
"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=="],
+ "conf/dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="],
+
+ "conf/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
+
+ "crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "db0/drizzle-orm": ["drizzle-orm@1.0.0-beta.12-a5629fb", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-wyOAgr9Cy9oEN6z5S0JGhfipLKbRRJtQKgbDO9SXGR9swMBbGNIlXkeMqPRrqYQ8k70mh+7ZJ/eVmJ2F7zR3Vg=="],
+
+ "defaults/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
+
+ "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "dir-compare/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+ "dmg-builder/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
+ "dmg-license/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
+
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
@@ -4706,6 +5386,18 @@
"editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="],
+ "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "electron-builder/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+ "electron-publish/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "electron-publish/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="],
+
+ "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
@@ -4714,6 +5406,10 @@
"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/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
+
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
@@ -4726,10 +5422,22 @@
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+ "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+ "fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+ "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "gauge/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "gauge/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
@@ -4740,12 +5448,16 @@
"happy-dom/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=="],
+ "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
+
"html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
"html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
+
"js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
@@ -4756,6 +5468,18 @@
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="],
+
+ "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
+
+ "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
+
+ "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
"md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
@@ -4768,10 +5492,34 @@
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
+ "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+ "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "motion/framer-motion": ["framer-motion@12.34.5", "", { "dependencies": { "motion-dom": "^12.34.5", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ=="],
+
"mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="],
+ "node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
+ "node-gyp/nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="],
+
+ "node-gyp/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
+
+ "node-gyp/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+
+ "node-gyp/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="],
@@ -4798,8 +5546,18 @@
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
+ "ora/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
+ "ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
+
+ "ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
+ "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
@@ -4810,18 +5568,34 @@
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+ "plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
+
"postcss-css-variables/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
+ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
+
+ "prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "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=="],
+ "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+ "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+
+ "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
"rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
@@ -4834,6 +5608,8 @@
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
+ "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
+
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
@@ -4842,12 +5618,22 @@
"sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
- "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+ "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
+
+ "sqlite3/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+
+ "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"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=="],
@@ -4858,10 +5644,18 @@
"tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
+ "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+
+ "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
+
"tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+ "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="],
+
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
+ "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
+
"token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
@@ -4876,10 +5670,16 @@
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
+ "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"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=="],
@@ -4890,6 +5690,8 @@
"which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
+ "wide-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
"wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -4908,6 +5710,8 @@
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
+ "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
+
"zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -5010,6 +5814,32 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+ "@develar/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
+ "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "@electron/fuses/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+ "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="],
+
+ "@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
+
+ "@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
+
+ "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "@electron/universal/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+ "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
+ "@electron/windows-sign/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
"@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
"@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
@@ -5070,6 +5900,8 @@
"@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+ "@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
@@ -5092,6 +5924,8 @@
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
+ "@npmcli/move-file/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="],
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
@@ -5178,6 +6012,8 @@
"@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
+ "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
+
"@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
"@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
@@ -5210,6 +6046,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=="],
@@ -5224,10 +6062,18 @@
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
+ "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
+
+ "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
+
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -5254,16 +6100,48 @@
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
+ "cacache/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
+
+ "cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+ "cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "crc/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "dir-compare/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+ "dmg-license/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
"editorconfig/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ "electron-builder/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "electron-builder/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+ "gauge/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
@@ -5272,10 +6150,32 @@
"js-beautify/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+ "lazystream/readable-stream/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
+
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
+ "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
+
+ "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
+
+ "motion/framer-motion/motion-dom": ["motion-dom@12.34.5", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA=="],
+
+ "node-gyp/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "node-gyp/nopt/abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
+
+ "node-gyp/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
+
+ "node-gyp/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
+
+ "node-gyp/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+ "node-gyp/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "node-gyp/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
@@ -5290,12 +6190,20 @@
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
+ "ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -5304,8 +6212,76 @@
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "sqlite3/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
+
+ "sqlite3/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
+
+ "sqlite3/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+ "sqlite3/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "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=="],
+ "tar-fs/tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
+ "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
+
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
@@ -5372,6 +6348,12 @@
"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=="],
+
+ "wide-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "wide-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"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=="],
@@ -5484,6 +6466,30 @@
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
+ "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="],
+
+ "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="],
+
+ "@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
+
+ "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "@electron/rebuild/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "@electron/rebuild/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -5536,6 +6542,8 @@
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
+ "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
@@ -5554,6 +6562,8 @@
"@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
+ "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
+
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -5562,6 +6572,8 @@
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
"archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
@@ -5580,10 +6592,28 @@
"babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+ "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"editorconfig/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "electron-builder/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "electron-builder/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "electron-builder/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "electron-builder/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"esbuild-plugin-copy/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"gray-matter/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
@@ -5592,6 +6622,10 @@
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+ "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
+ "node-gyp/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
@@ -5614,6 +6648,8 @@
"opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
+ "ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
@@ -5626,10 +6662,18 @@
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+ "sqlite3/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "tar-fs/tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
+ "wide-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"@astrojs/check/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -5662,8 +6706,28 @@
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="],
+
+ "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
@@ -5676,12 +6740,20 @@
"babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "electron-builder/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"js-beautify/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "node-gyp/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
@@ -5694,10 +6766,24 @@
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "tar-fs/tar-stream/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename/unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="],
+
+ "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -5709,5 +6795,21 @@
"rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
+ "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
}
}
diff --git a/flake.lock b/flake.lock
index 9efa1883b1..59eb118fa4 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1770812194,
- "narHash": "sha256-OH+lkaIKAvPXR3nITO7iYZwew2nW9Y7Xxq0yfM/UcUU=",
+ "lastModified": 1772091128,
+ "narHash": "sha256-TnrYykX8Mf/Ugtkix6V+PjW7miU2yClA6uqWl/v6KWM=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "8482c7ded03bae7550f3d69884f1e611e3bd19e8",
+ "rev": "3f0336406035444b4a24b942788334af5f906259",
"type": "github"
},
"original": {
diff --git a/github/index.ts b/github/index.ts
index da310178a7..1a0a992622 100644
--- a/github/index.ts
+++ b/github/index.ts
@@ -8,6 +8,7 @@ import type { Context as GitHubContext } from "@actions/github/lib/context"
import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { spawn } from "node:child_process"
+import { setTimeout as sleep } from "node:timers/promises"
type GitHubAuthor = {
login: string
@@ -281,7 +282,7 @@ async function assertOpencodeConnected() {
connected = true
break
} catch (e) {}
- await Bun.sleep(300)
+ await sleep(300)
} while (retry++ < 30)
if (!connected) {
diff --git a/infra/console.ts b/infra/console.ts
index 3f3c2b8d93..128e069863 100644
--- a/infra/console.ts
+++ b/infra/console.ts
@@ -100,29 +100,47 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
],
})
-const zenProduct = new stripe.Product("ZenBlack", {
+const zenLiteProduct = new stripe.Product("ZenLite", {
+ name: "OpenCode Go",
+})
+const zenLitePrice = new stripe.Price("ZenLitePrice", {
+ product: zenLiteProduct.id,
+ currency: "usd",
+ recurring: {
+ interval: "month",
+ intervalCount: 1,
+ },
+ unitAmount: 1000,
+})
+const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
+ properties: {
+ product: zenLiteProduct.id,
+ price: zenLitePrice.id,
+ },
+})
+
+const zenBlackProduct = new stripe.Product("ZenBlack", {
name: "OpenCode Black",
})
-const zenPriceProps = {
- product: zenProduct.id,
+const zenBlackPriceProps = {
+ product: zenBlackProduct.id,
currency: "usd",
recurring: {
interval: "month",
intervalCount: 1,
},
}
-const zenPrice200 = new stripe.Price("ZenBlackPrice", { ...zenPriceProps, unitAmount: 20000 })
-const zenPrice100 = new stripe.Price("ZenBlack100Price", { ...zenPriceProps, unitAmount: 10000 })
-const zenPrice20 = new stripe.Price("ZenBlack20Price", { ...zenPriceProps, unitAmount: 2000 })
+const zenBlackPrice200 = new stripe.Price("ZenBlackPrice", { ...zenBlackPriceProps, unitAmount: 20000 })
+const zenBlackPrice100 = new stripe.Price("ZenBlack100Price", { ...zenBlackPriceProps, unitAmount: 10000 })
+const zenBlackPrice20 = new stripe.Price("ZenBlack20Price", { ...zenBlackPriceProps, unitAmount: 2000 })
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
properties: {
- product: zenProduct.id,
- plan200: zenPrice200.id,
- plan100: zenPrice100.id,
- plan20: zenPrice20.id,
+ product: zenBlackProduct.id,
+ plan200: zenBlackPrice200.id,
+ plan100: zenBlackPrice100.id,
+ plan20: zenBlackPrice20.id,
},
})
-const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS1"),
@@ -195,7 +213,8 @@ new sst.cloudflare.x.SolidStart("Console", {
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
ZEN_BLACK_PRICE,
- ZEN_BLACK_LIMITS,
+ ZEN_LITE_PRICE,
+ new sst.Secret("ZEN_LIMITS"),
new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS,
...($dev
diff --git a/nix/hashes.json b/nix/hashes.json
index d07b8f0f6b..2190feb256 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,8 +1,8 @@
{
"nodeModules": {
- "x86_64-linux": "sha256-fjrvCgQ2PHYxzw8NsiEHOcor46qN95/cfilFHFqCp/k=",
- "aarch64-linux": "sha256-xWp4LLJrbrCPFL1F6SSbProq/t/az4CqhTcymPvjOBQ=",
- "aarch64-darwin": "sha256-Wbfyy/bruFHKUWsyJ2aiPXAzLkk5MNBfN6QdGPQwZS0=",
- "x86_64-darwin": "sha256-wDnMbiaBCRj5STkaLoVCZTdXVde+/YKfwWzwJZ1AJXQ="
+ "x86_64-linux": "sha256-+SMpaj0jeIHjlddAu6QIwojmWFVIiA8/G32hiQMjcOk=",
+ "aarch64-linux": "sha256-uo63IF6OCMab+O3ngn1sVxqIGJMm04HXuDgIRmXNTNk=",
+ "aarch64-darwin": "sha256-yB2tWm6AsX6UifnDqe7VldhN5zTQkDoqZ87AGQYjxT4=",
+ "x86_64-darwin": "sha256-nNhtqMSG4/y+uxjj14Jc5QQ7X6hQli9ni4v56XAvaAU="
}
}
diff --git a/nix/node_modules.nix b/nix/node_modules.nix
index e918846c24..6c188c07cf 100644
--- a/nix/node_modules.nix
+++ b/nix/node_modules.nix
@@ -31,6 +31,7 @@ stdenvNoCC.mkDerivation {
../package.json
../patches
../install # required by desktop build (cli.rs include_str!)
+ ../.github/TEAM_MEMBERS # required by @opencode-ai/script
]
);
};
diff --git a/package.json b/package.json
index 2e7c1172aa..530ab937c2 100644
--- a/package.json
+++ b/package.json
@@ -4,11 +4,12 @@
"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",
"dev:web": "bun --cwd packages/app dev",
+ "dev:storybook": "bun --cwd packages/storybook storybook",
"typecheck": "bun turbo typecheck",
"prepare": "husky",
"random": "echo 'Random script'",
@@ -35,13 +36,13 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
- "@pierre/diffs": "1.1.0-beta.13",
+ "@pierre/diffs": "1.1.0-beta.18",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
"dompurify": "3.3.1",
- "drizzle-kit": "1.0.0-beta.12-a5629fb",
- "drizzle-orm": "1.0.0-beta.12-a5629fb",
+ "drizzle-kit": "1.0.0-beta.16-ea816b6",
+ "drizzle-orm": "1.0.0-beta.16-ea816b6",
"ai": "5.0.124",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
@@ -70,12 +71,13 @@
"@actions/artifact": "5.0.1",
"@tsconfig/bun": "catalog:",
"@types/mime-types": "3.0.1",
+ "@typescript/native-preview": "catalog:",
"glob": "13.0.5",
"husky": "9.1.7",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",
- "turbo": "2.5.6"
+ "turbo": "2.8.13"
},
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
@@ -98,7 +100,8 @@
"protobufjs",
"tree-sitter",
"tree-sitter-bash",
- "web-tree-sitter"
+ "web-tree-sitter",
+ "electron"
],
"overrides": {
"@types/bun": "catalog:",
diff --git a/packages/app/create-effect-simplification-spec.md b/packages/app/create-effect-simplification-spec.md
new file mode 100644
index 0000000000..cc101ab059
--- /dev/null
+++ b/packages/app/create-effect-simplification-spec.md
@@ -0,0 +1,515 @@
+# CreateEffect Simplification Implementation Spec
+
+Reduce reactive misuse across `packages/app`.
+
+---
+
+## Context
+
+This work targets `packages/app/src`, which currently has 101 `createEffect` calls across 37 files.
+
+The biggest clusters are `pages/session.tsx` (19), `pages/layout.tsx` (13), `pages/session/file-tabs.tsx` (6), and several context providers that mirror one store into another.
+
+Key issues from the audit:
+
+- Derived state is being written through effects instead of computed directly
+- Session and file resets are handled by watch-and-clear effects instead of keyed state boundaries
+- User-driven actions are hidden inside reactive effects
+- Context layers mirror and hydrate child stores with multiple sync effects
+- Several areas repeat the same imperative trigger pattern in multiple effects
+
+Keep the implementation focused on removing unnecessary effects, not on broad UI redesign.
+
+## Goals
+
+- Cut high-churn `createEffect` usage in the hottest files first
+- Replace effect-driven derived state with reactive derivation
+- Replace reset-on-key effects with keyed ownership boundaries
+- Move event-driven work to direct actions and write paths
+- Remove mirrored store hydration where a single source of truth can exist
+- Leave necessary external sync effects in place, but make them narrower and clearer
+
+## Non-Goals
+
+- Do not rewrite unrelated component structure just to reduce the count
+- Do not change product behavior, navigation flow, or persisted data shape unless required for a cleaner write boundary
+- Do not remove effects that bridge to DOM, editors, polling, or external APIs unless there is a clearly safer equivalent
+- Do not attempt a repo-wide cleanup outside `packages/app`
+
+## Effect Taxonomy And Replacement Rules
+
+Use these rules during implementation.
+
+### Prefer `createMemo`
+
+Use `createMemo` when the target value is pure derived state from other signals or stores.
+
+Do this when an effect only reads reactive inputs and writes another reactive value that could be computed instead.
+
+Apply this to:
+
+- `packages/app/src/pages/session.tsx:141`
+- `packages/app/src/pages/layout.tsx:557`
+- `packages/app/src/components/terminal.tsx:261`
+- `packages/app/src/components/session/session-header.tsx:309`
+
+Rules:
+
+- If no external system is touched, do not use `createEffect`
+- Derive once, then read the memo where needed
+- If normalization is required, prefer normalizing at the write boundary before falling back to a memo
+
+### Prefer Keyed Remounts
+
+Use keyed remounts when local UI state should reset because an identity changed.
+
+Do this with `sessionKey`, `scope()`, or another stable identity instead of watching the key and manually clearing signals.
+
+Apply this to:
+
+- `packages/app/src/pages/session.tsx:325`
+- `packages/app/src/pages/session.tsx:336`
+- `packages/app/src/pages/session.tsx:477`
+- `packages/app/src/pages/session.tsx:869`
+- `packages/app/src/pages/session.tsx:963`
+- `packages/app/src/pages/session/message-timeline.tsx:149`
+- `packages/app/src/context/file.tsx:100`
+
+Rules:
+
+- If the desired behavior is "new identity, fresh local state," key the owner subtree
+- Keep state local to the keyed boundary so teardown and recreation handle the reset naturally
+
+### Prefer Event Handlers And Actions
+
+Use direct handlers, store actions, and async command functions when work happens because a user clicked, selected, reloaded, or navigated.
+
+Do this when an effect is just watching for a flag change, command token, or event-bus signal to trigger imperative logic.
+
+Apply this to:
+
+- `packages/app/src/pages/layout.tsx:484`
+- `packages/app/src/pages/layout.tsx:652`
+- `packages/app/src/pages/layout.tsx:776`
+- `packages/app/src/pages/layout.tsx:1489`
+- `packages/app/src/pages/layout.tsx:1519`
+- `packages/app/src/components/file-tree.tsx:328`
+- `packages/app/src/pages/session/terminal-panel.tsx:55`
+- `packages/app/src/context/global-sync.tsx:148`
+- Duplicated trigger sets in:
+ - `packages/app/src/pages/session/review-tab.tsx:122`
+ - `packages/app/src/pages/session/review-tab.tsx:130`
+ - `packages/app/src/pages/session/review-tab.tsx:138`
+ - `packages/app/src/pages/session/file-tabs.tsx:367`
+ - `packages/app/src/pages/session/file-tabs.tsx:378`
+ - `packages/app/src/pages/session/file-tabs.tsx:389`
+ - `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
+ - `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
+ - `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
+
+Rules:
+
+- If the trigger is user intent, call the action at the source of that intent
+- If the same imperative work is triggered from multiple places, extract one function and call it directly
+
+### Prefer `onMount` And `onCleanup`
+
+Use `onMount` and `onCleanup` for lifecycle-only setup and teardown.
+
+This is the right fit for subscriptions, one-time wiring, timers, and imperative integration that should not rerun for ordinary reactive changes.
+
+Use this when:
+
+- Setup should happen once per owner lifecycle
+- Cleanup should always pair with teardown
+- The work is not conceptually derived state
+
+### Keep `createEffect` When It Is A Real Bridge
+
+Keep `createEffect` when it synchronizes reactive data to an external imperative sink.
+
+Examples that should remain, though they may be narrowed or split:
+
+- DOM/editor sync in `packages/app/src/components/prompt-input.tsx:690`
+- Scroll sync in `packages/app/src/pages/session.tsx:685`
+- Scroll/hash sync in `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
+- External sync in:
+ - `packages/app/src/context/language.tsx:207`
+ - `packages/app/src/context/settings.tsx:110`
+ - `packages/app/src/context/sdk.tsx:26`
+- Polling in:
+ - `packages/app/src/components/status-popover.tsx:59`
+ - `packages/app/src/components/dialog-select-server.tsx:273`
+
+Rules:
+
+- Keep the effect single-purpose
+- Make dependencies explicit and narrow
+- Avoid writing back into the same reactive graph unless absolutely required
+
+## Implementation Plan
+
+### Phase 0: Classification Pass
+
+Before changing code, tag each targeted effect as one of: derive, reset, event, lifecycle, or external bridge.
+
+Acceptance criteria:
+
+- Every targeted effect in this spec is tagged with a replacement strategy before refactoring starts
+- Shared helpers to be introduced are identified up front to avoid repeating patterns
+
+### Phase 1: Derived-State Cleanup
+
+Tackle highest-value, lowest-risk derived-state cleanup first.
+
+Priority items:
+
+- Normalize tabs at write boundaries and remove `packages/app/src/pages/session.tsx:141`
+- Stop syncing `workspaceOrder` in `packages/app/src/pages/layout.tsx:557`
+- Make prompt slash filtering reactive so `packages/app/src/components/prompt-input.tsx:652` can be removed
+- Replace other obvious derived-state effects in terminal and session header
+
+Acceptance criteria:
+
+- No behavior change in tab ordering, prompt filtering, terminal display, or header state
+- Targeted derived-state effects are deleted, not just moved
+
+### Phase 2: Keyed Reset Cleanup
+
+Replace reset-on-key effects with keyed ownership boundaries.
+
+Priority items:
+
+- Key session-scoped UI and state by `sessionKey`
+- Key file-scoped state by `scope()`
+- Remove manual clear-and-reseed effects in session and file context
+
+Acceptance criteria:
+
+- Switching session or file scope recreates the intended local state cleanly
+- No stale state leaks across session or scope changes
+- Target reset effects are deleted
+
+### Phase 3: Event-Driven Work Extraction
+
+Move event-driven work out of reactive effects.
+
+Priority items:
+
+- Replace `globalStore.reload` effect dispatching with direct calls
+- Split mixed-responsibility effect in `packages/app/src/pages/layout.tsx:1489`
+- Collapse duplicated imperative trigger triplets into single functions
+- Move file-tree and terminal-panel imperative work to explicit handlers
+
+Acceptance criteria:
+
+- User-triggered behavior still fires exactly once per intended action
+- No effect remains whose only job is to notice a command-like state and trigger an imperative function
+
+### Phase 4: Context Ownership Cleanup
+
+Remove mirrored child-store hydration patterns.
+
+Priority items:
+
+- Remove child-store hydration mirrors in `packages/app/src/context/global-sync/child-store.ts:184`, `:190`, `:193`
+- Simplify mirror logic in `packages/app/src/context/global-sync.tsx:130`, `:138`
+- Revisit `packages/app/src/context/layout.tsx:424` if it still mirrors instead of deriving
+
+Acceptance criteria:
+
+- There is one clear source of truth for each synced value
+- Child stores no longer need effect-based hydration to stay consistent
+- Initialization and updates both work without manual mirror effects
+
+### Phase 5: Cleanup And Keeper Review
+
+Clean up remaining targeted hotspots and narrow the effects that should stay.
+
+Acceptance criteria:
+
+- Remaining `createEffect` calls in touched files are all true bridges or clearly justified lifecycle sync
+- Mixed-responsibility effects are split into smaller units where still needed
+
+## Detailed Work Items By Area
+
+### 1. Normalize Tab State
+
+Files:
+
+- `packages/app/src/pages/session.tsx:141`
+
+Work:
+
+- Move tab normalization into the functions that create, load, or update tab state
+- Make readers consume already-normalized tab data
+- Remove the effect that rewrites derived tab state after the fact
+
+Rationale:
+
+- Tabs should become valid when written, not be repaired later
+- This removes a feedback loop and makes state easier to trust
+
+Acceptance criteria:
+
+- The effect at `packages/app/src/pages/session.tsx:141` is removed
+- Newly created and restored tabs are normalized before they enter local state
+- Tab rendering still matches current behavior for valid and edge-case inputs
+
+### 2. Key Session-Owned State
+
+Files:
+
+- `packages/app/src/pages/session.tsx:325`
+- `packages/app/src/pages/session.tsx:336`
+- `packages/app/src/pages/session.tsx:477`
+- `packages/app/src/pages/session.tsx:869`
+- `packages/app/src/pages/session.tsx:963`
+- `packages/app/src/pages/session/message-timeline.tsx:149`
+
+Work:
+
+- Identify state that should reset when `sessionKey` changes
+- Move that state under a keyed subtree or keyed owner boundary
+- Remove effects that watch `sessionKey` just to clear local state, refs, or temporary UI flags
+
+Rationale:
+
+- Session identity already defines the lifetime of this UI state
+- Keyed ownership makes reset behavior automatic and easier to reason about
+
+Acceptance criteria:
+
+- The targeted reset effects are removed
+- Changing sessions resets only the intended session-local state
+- Scroll and editor state that should persist are not accidentally reset
+
+### 3. Derive Workspace Order
+
+Files:
+
+- `packages/app/src/pages/layout.tsx:557`
+
+Work:
+
+- Stop writing `workspaceOrder` from live workspace data in an effect
+- Represent user overrides separately from live workspace data
+- Compute effective order from current data plus overrides with a memo or pure helper
+
+Rationale:
+
+- Persisted user intent and live source data should not mirror each other through an effect
+- A computed effective order avoids drift and racey resync behavior
+
+Acceptance criteria:
+
+- The effect at `packages/app/src/pages/layout.tsx:557` is removed
+- Workspace order updates correctly when workspaces appear, disappear, or are reordered by the user
+- User overrides persist without requiring a sync-back effect
+
+### 4. Remove Child-Store Mirrors
+
+Files:
+
+- `packages/app/src/context/global-sync.tsx:130`
+- `packages/app/src/context/global-sync.tsx:138`
+- `packages/app/src/context/global-sync.tsx:148`
+- `packages/app/src/context/global-sync/child-store.ts:184`
+- `packages/app/src/context/global-sync/child-store.ts:190`
+- `packages/app/src/context/global-sync/child-store.ts:193`
+- `packages/app/src/context/layout.tsx:424`
+
+Work:
+
+- Trace the actual ownership of global and child store values
+- Replace hydration and mirror effects with explicit initialization and direct updates
+- Remove the `globalStore.reload` event-bus pattern and call the needed reload paths directly
+
+Rationale:
+
+- Mirrors make it hard to tell which state is authoritative
+- Event-bus style state toggles hide control flow and create accidental reruns
+
+Acceptance criteria:
+
+- Child store hydration no longer depends on effect-based copying
+- Reload work can be followed from the event source to the handler without a reactive relay
+- State remains correct on first load, child creation, and subsequent updates
+
+### 5. Key File-Scoped State
+
+Files:
+
+- `packages/app/src/context/file.tsx:100`
+
+Work:
+
+- Move file-scoped local state under a boundary keyed by `scope()`
+- Remove any effect that watches `scope()` only to reset file-local state
+
+Rationale:
+
+- File scope changes are identity changes
+- Keyed ownership gives a cleaner reset than manual clear logic
+
+Acceptance criteria:
+
+- The effect at `packages/app/src/context/file.tsx:100` is removed
+- Switching scopes resets only scope-local state
+- No previous-scope data appears after a scope change
+
+### 6. Split Layout Side Effects
+
+Files:
+
+- `packages/app/src/pages/layout.tsx:1489`
+- Related event-driven effects near `packages/app/src/pages/layout.tsx:484`, `:652`, `:776`, `:1519`
+
+Work:
+
+- Break the mixed-responsibility effect at `:1489` into direct actions and smaller bridge effects only where required
+- Move user-triggered branches into the actual command or handler that causes them
+- Remove any branch that only exists because one effect is handling unrelated concerns
+
+Rationale:
+
+- Mixed effects hide cause and make reruns hard to predict
+- Smaller units reduce accidental coupling and make future cleanup safer
+
+Acceptance criteria:
+
+- The effect at `packages/app/src/pages/layout.tsx:1489` no longer mixes unrelated responsibilities
+- Event-driven branches execute from direct handlers
+- Remaining effects in this area each have one clear external sync purpose
+
+### 7. Remove Duplicate Triggers
+
+Files:
+
+- `packages/app/src/pages/session/review-tab.tsx:122`
+- `packages/app/src/pages/session/review-tab.tsx:130`
+- `packages/app/src/pages/session/review-tab.tsx:138`
+- `packages/app/src/pages/session/file-tabs.tsx:367`
+- `packages/app/src/pages/session/file-tabs.tsx:378`
+- `packages/app/src/pages/session/file-tabs.tsx:389`
+- `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
+- `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
+- `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
+
+Work:
+
+- Extract one explicit imperative function per behavior
+- Call that function from each source event instead of replicating the same effect pattern multiple times
+- Preserve the scroll-sync effect that is truly syncing with the DOM, but remove duplicate trigger scaffolding around it
+
+Rationale:
+
+- Duplicate triggers make it easy to miss a case or fire twice
+- One named action is easier to test and reason about
+
+Acceptance criteria:
+
+- Repeated imperative effect triplets are collapsed into shared functions
+- Scroll behavior still works, including hash-based navigation
+- No duplicate firing is introduced
+
+### 8. Make Prompt Filtering Reactive
+
+Files:
+
+- `packages/app/src/components/prompt-input.tsx:652`
+- Keep `packages/app/src/components/prompt-input.tsx:690` as needed
+
+Work:
+
+- Convert slash filtering into a pure reactive derivation from the current input and candidate command list
+- Keep only the editor or DOM bridge effect if it is still needed for imperative syncing
+
+Rationale:
+
+- Filtering is classic derived state
+- It should not need an effect if it can be computed from current inputs
+
+Acceptance criteria:
+
+- The effect at `packages/app/src/components/prompt-input.tsx:652` is removed
+- Filtered slash-command results update correctly as the input changes
+- The editor sync effect at `:690` still behaves correctly
+
+### 9. Clean Up Smaller Derived-State Cases
+
+Files:
+
+- `packages/app/src/components/terminal.tsx:261`
+- `packages/app/src/components/session/session-header.tsx:309`
+
+Work:
+
+- Replace effect-written local state with memos or inline derivation
+- Remove intermediate setters when the value can be computed directly
+
+Rationale:
+
+- These are low-risk wins that reinforce the same pattern
+- They also help keep follow-up cleanup consistent
+
+Acceptance criteria:
+
+- Targeted effects are removed
+- UI output remains unchanged under the same inputs
+
+## Verification And Regression Checks
+
+Run focused checks after each phase, not only at the end.
+
+### Suggested Verification
+
+- Switch between sessions rapidly and confirm local session UI resets only where intended
+- Open, close, and reorder tabs and confirm order and normalization remain stable
+- Change workspaces, reload workspace data, and verify effective ordering is correct
+- Change file scope and confirm stale file state does not bleed across scopes
+- Trigger layout actions that previously depended on effects and confirm they still fire once
+- Use slash commands in the prompt and verify filtering updates as you type
+- Test review tab, file tab, and hash-scroll flows for duplicate or missing triggers
+- Verify global sync initialization, reload, and child-store creation paths
+
+### Regression Checks
+
+- No accidental infinite reruns
+- No double-firing network or command actions
+- No lost cleanup for listeners, timers, or scroll handlers
+- No preserved stale state after identity changes
+- No removed effect that was actually bridging to DOM or an external API
+
+If available, add or update tests around pure helpers introduced during this cleanup.
+
+Favor tests for derived ordering, normalization, and action extraction, since those are easiest to lock down.
+
+## Definition Of Done
+
+This work is done when all of the following are true:
+
+- The highest-leverage targets in this spec are implemented
+- Each removed effect has been replaced by a clearer pattern: memo, keyed boundary, direct action, or lifecycle hook
+- The "should remain" effects still exist only where they serve a real external sync purpose
+- Touched files have fewer mixed-responsibility effects and clearer ownership of state
+- Manual verification covers session switching, file scope changes, workspace ordering, prompt filtering, and reload flows
+- No behavior regressions are found in the targeted areas
+
+A reduced raw `createEffect` count is helpful, but it is not the main success metric.
+
+The main success metric is clearer ownership and fewer effect-driven state repairs.
+
+## Risks And Rollout Notes
+
+Main risks:
+
+- Keyed remounts can reset too much if state boundaries are drawn too high
+- Store mirror removal can break initialization order if ownership is not mapped first
+- Moving event work out of effects can accidentally skip triggers that were previously implicit
+
+Rollout notes:
+
+- Land in small phases, with each phase keeping the app behaviorally stable
+- Prefer isolated PRs by phase or by file cluster, especially for context-store changes
+- Review each remaining effect in touched files and leave it only if it clearly bridges to something external
diff --git a/packages/app/e2e/AGENTS.md b/packages/app/e2e/AGENTS.md
index 59662dbea5..8bfbd111b2 100644
--- a/packages/app/e2e/AGENTS.md
+++ b/packages/app/e2e/AGENTS.md
@@ -71,6 +71,12 @@ test("test description", async ({ page, sdk, gotoSession }) => {
- `closeDialog(page, dialog)` - Close any dialog
- `openSidebar(page)` / `closeSidebar(page)` - Toggle sidebar
- `withSession(sdk, title, callback)` - Create temp session
+- `withProject(...)` - Create temp project/workspace
+- `sessionIDFromUrl(url)` - Read session ID from URL
+- `slugFromUrl(url)` - Read workspace slug from URL
+- `waitSlug(page, skip?)` - Wait for resolved workspace slug
+- `trackSession(sessionID, directory?)` - Register session for fixture cleanup
+- `trackDirectory(directory)` - Register directory for fixture cleanup
- `clickListItem(container, filter)` - Click list item by key/text
**Selectors** (`selectors.ts`):
@@ -109,7 +115,7 @@ import { test, expect } from "@playwright/test"
### Error Handling
-Tests should clean up after themselves:
+Tests should clean up after themselves. Prefer fixture-managed cleanup:
```typescript
test("test with cleanup", async ({ page, sdk, gotoSession }) => {
@@ -120,6 +126,11 @@ test("test with cleanup", async ({ page, sdk, gotoSession }) => {
})
```
+- Prefer `withSession(...)` for temp sessions
+- In `withProject(...)` tests that create sessions or extra workspaces, call `trackSession(sessionID, directory?)` and `trackDirectory(directory)`
+- This lets fixture teardown abort, wait for idle, and clean up safely under CI concurrency
+- Avoid calling `sdk.session.delete(...)` directly
+
### Timeouts
Default: 60s per test, 10s per assertion. Override when needed:
@@ -161,9 +172,10 @@ await page.keyboard.press(`${modKey}+Comma`) // Open settings
1. Choose appropriate folder or create new one
2. Import from `../fixtures`
3. Use helper functions from `../actions` and `../selectors`
-4. Clean up any created resources
-5. Use specific selectors (avoid CSS classes)
-6. Test one feature per test file
+4. When validating routing, use shared helpers from `../actions`. Workspace URL slugs can be canonicalized on Windows, so assert against canonical or resolved workspace slugs.
+5. Clean up any created resources
+6. Use specific selectors (avoid CSS classes)
+7. Test one feature per test file
## Local Development
diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts
index a7ccba6175..86147dc65d 100644
--- a/packages/app/e2e/actions.ts
+++ b/packages/app/e2e/actions.ts
@@ -3,12 +3,12 @@ import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { execSync } from "node:child_process"
-import { modKey, serverUrl } from "./utils"
+import { createSdk, modKey, resolveDirectory, serverUrl } from "./utils"
import {
- sessionItemSelector,
dropdownMenuTriggerSelector,
dropdownMenuContentSelector,
projectMenuTriggerSelector,
+ projectCloseMenuSelector,
projectWorkspacesToggleSelector,
titlebarRightSelector,
popoverBodySelector,
@@ -18,7 +18,6 @@ import {
workspaceItemSelector,
workspaceMenuTriggerSelector,
} from "./selectors"
-import type { createSdk } from "./utils"
export async function defocus(page: Page) {
await page
@@ -61,9 +60,9 @@ export async function closeDialog(page: Page, dialog: Locator) {
}
export async function isSidebarClosed(page: Page) {
- const main = page.locator("main")
- const classes = (await main.getAttribute("class")) ?? ""
- return classes.includes("xl:border-l")
+ const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
+ await expect(button).toBeVisible()
+ return (await button.getAttribute("aria-expanded")) !== "true"
}
export async function toggleSidebar(page: Page) {
@@ -75,48 +74,34 @@ export async function openSidebar(page: Page) {
if (!(await isSidebarClosed(page))) return
const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
- const visible = await button
- .isVisible()
- .then((x) => x)
- .catch(() => false)
+ await button.click()
- if (visible) await button.click()
- if (!visible) await toggleSidebar(page)
-
- const main = page.locator("main")
- const opened = await expect(main)
- .not.toHaveClass(/xl:border-l/, { timeout: 1500 })
+ const opened = await expect(button)
+ .toHaveAttribute("aria-expanded", "true", { timeout: 1500 })
.then(() => true)
.catch(() => false)
if (opened) return
await toggleSidebar(page)
- await expect(main).not.toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "true")
}
export async function closeSidebar(page: Page) {
if (await isSidebarClosed(page)) return
const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
- const visible = await button
- .isVisible()
- .then((x) => x)
- .catch(() => false)
+ await button.click()
- if (visible) await button.click()
- if (!visible) await toggleSidebar(page)
-
- const main = page.locator("main")
- const closed = await expect(main)
- .toHaveClass(/xl:border-l/, { timeout: 1500 })
+ const closed = await expect(button)
+ .toHaveAttribute("aria-expanded", "false", { timeout: 1500 })
.then(() => true)
.catch(() => false)
if (closed) return
await toggleSidebar(page)
- await expect(main).toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "false")
}
export async function openSettings(page: Page) {
@@ -197,17 +182,48 @@ export async function createTestProject() {
await fs.writeFile(path.join(root, "README.md"), "# e2e\n")
execSync("git init", { cwd: root, stdio: "ignore" })
+ execSync("git config core.fsmonitor false", { cwd: root, stdio: "ignore" })
execSync("git add -A", { cwd: root, stdio: "ignore" })
execSync('git -c user.name="e2e" -c user.email="e2e@example.com" commit -m "init" --allow-empty', {
cwd: root,
stdio: "ignore",
})
- return root
+ return resolveDirectory(root)
}
export async function cleanupTestProject(directory: string) {
- await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined)
+ try {
+ execSync("git fsmonitor--daemon stop", { cwd: directory, stdio: "ignore" })
+ } catch {}
+ await fs.rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => undefined)
+}
+
+export function slugFromUrl(url: string) {
+ return /\/([^/]+)\/session(?:[/?#]|$)/.exec(url)?.[1] ?? ""
+}
+
+export async function waitSlug(page: Page, skip: string[] = []) {
+ let prev = ""
+ let next = ""
+ await expect
+ .poll(
+ () => {
+ const slug = slugFromUrl(page.url())
+ if (!slug) return ""
+ if (skip.includes(slug)) return ""
+ if (slug !== prev) {
+ prev = slug
+ next = ""
+ return ""
+ }
+ next = slug
+ return slug
+ },
+ { timeout: 45_000 },
+ )
+ .not.toBe("")
+ return next
}
export function sessionIDFromUrl(url: string) {
@@ -216,7 +232,7 @@ export function sessionIDFromUrl(url: string) {
}
export async function hoverSessionItem(page: Page, sessionID: string) {
- const sessionEl = page.locator(sessionItemSelector(sessionID)).first()
+ const sessionEl = page.locator(`[data-session-id="${sessionID}"]`).last()
await expect(sessionEl).toBeVisible()
await sessionEl.hover()
return sessionEl
@@ -317,6 +333,57 @@ export async function clickListItem(
return item
}
+async function status(sdk: ReturnType, sessionID: string) {
+ const data = await sdk.session
+ .status()
+ .then((x) => x.data ?? {})
+ .catch(() => undefined)
+ return data?.[sessionID]
+}
+
+async function stable(sdk: ReturnType, sessionID: string, timeout = 10_000) {
+ let prev = ""
+ await expect
+ .poll(
+ async () => {
+ const info = await sdk.session
+ .get({ sessionID })
+ .then((x) => x.data)
+ .catch(() => undefined)
+ if (!info) return true
+ const next = `${info.title}:${info.time.updated ?? info.time.created}`
+ if (next !== prev) {
+ prev = next
+ return false
+ }
+ return true
+ },
+ { timeout },
+ )
+ .toBe(true)
+}
+
+export async function waitSessionIdle(sdk: ReturnType, sessionID: string, timeout = 30_000) {
+ await expect.poll(() => status(sdk, sessionID).then((x) => !x || x.type === "idle"), { timeout }).toBe(true)
+}
+
+export async function cleanupSession(input: {
+ sessionID: string
+ directory?: string
+ sdk?: ReturnType
+}) {
+ const sdk = input.sdk ?? (input.directory ? createSdk(input.directory) : undefined)
+ if (!sdk) throw new Error("cleanupSession requires sdk or directory")
+ await waitSessionIdle(sdk, input.sessionID, 5_000).catch(() => undefined)
+ const current = await status(sdk, input.sessionID).catch(() => undefined)
+ if (current && current.type !== "idle") {
+ await sdk.session.abort({ sessionID: input.sessionID }).catch(() => undefined)
+ await waitSessionIdle(sdk, input.sessionID).catch(() => undefined)
+ }
+ await stable(sdk, input.sessionID).catch(() => undefined)
+ await sdk.session.delete({ sessionID: input.sessionID }).catch(() => undefined)
+}
+
export async function withSession(
sdk: ReturnType,
title: string,
@@ -328,7 +395,7 @@ export async function withSession(
try {
return await callback(session)
} finally {
- await sdk.session.delete({ sessionID: session.id }).catch(() => undefined)
+ await cleanupSession({ sdk, sessionID: session.id })
}
}
@@ -441,6 +508,57 @@ export async function seedSessionPermission(
return { id: result.id }
}
+export async function seedSessionTask(
+ sdk: ReturnType,
+ input: {
+ sessionID: string
+ description: string
+ prompt: string
+ subagentType?: string
+ },
+) {
+ const text = [
+ "Your only valid response is one task tool call.",
+ `Use this JSON input: ${JSON.stringify({
+ description: input.description,
+ prompt: input.prompt,
+ subagent_type: input.subagentType ?? "general",
+ })}`,
+ "Do not output plain text.",
+ "Wait for the task to start and return the child session id.",
+ ].join("\n")
+
+ const result = await seed({
+ sdk,
+ sessionID: input.sessionID,
+ prompt: text,
+ timeout: 90_000,
+ probe: async () => {
+ const messages = await sdk.session.messages({ sessionID: input.sessionID, limit: 50 }).then((x) => x.data ?? [])
+ const part = messages
+ .flatMap((message) => message.parts)
+ .find((part) => {
+ if (part.type !== "tool" || part.tool !== "task") return false
+ if (part.state.input?.description !== input.description) return false
+ return typeof part.state.metadata?.sessionId === "string" && part.state.metadata.sessionId.length > 0
+ })
+
+ if (!part) return
+ const id = part.state.metadata?.sessionId
+ if (typeof id !== "string" || !id) return
+ const child = await sdk.session
+ .get({ sessionID: id })
+ .then((x) => x.data)
+ .catch(() => undefined)
+ if (!child?.id) return
+ return { sessionID: id }
+ },
+ })
+
+ if (!result) throw new Error("Timed out seeding task tool")
+ return result
+}
+
export async function seedSessionTodos(
sdk: ReturnType,
input: {
@@ -515,32 +633,42 @@ export async function openProjectMenu(page: Page, projectSlug: string) {
const trigger = page.locator(projectMenuTriggerSelector(projectSlug)).first()
await expect(trigger).toHaveCount(1)
+ const menu = page
+ .locator(dropdownMenuContentSelector)
+ .filter({ has: page.locator(projectCloseMenuSelector(projectSlug)) })
+ .first()
+ const close = menu.locator(projectCloseMenuSelector(projectSlug)).first()
+
+ const clicked = await trigger
+ .click({ timeout: 1500 })
+ .then(() => true)
+ .catch(() => false)
+
+ if (clicked) {
+ const opened = await menu
+ .waitFor({ state: "visible", timeout: 1500 })
+ .then(() => true)
+ .catch(() => false)
+ if (opened) {
+ await expect(close).toBeVisible()
+ return menu
+ }
+ }
+
await trigger.focus()
await page.keyboard.press("Enter")
- const menu = page.locator(dropdownMenuContentSelector).first()
const opened = await menu
.waitFor({ state: "visible", timeout: 1500 })
.then(() => true)
.catch(() => false)
if (opened) {
- const viewport = page.viewportSize()
- const x = viewport ? Math.max(viewport.width - 5, 0) : 1200
- const y = viewport ? Math.max(viewport.height - 5, 0) : 800
- await page.mouse.move(x, y)
+ await expect(close).toBeVisible()
return menu
}
- await trigger.click({ force: true })
-
- await expect(menu).toBeVisible()
-
- const viewport = page.viewportSize()
- const x = viewport ? Math.max(viewport.width - 5, 0) : 1200
- const y = viewport ? Math.max(viewport.height - 5, 0) : 800
- await page.mouse.move(x, y)
- return menu
+ throw new Error(`Failed to open project menu: ${projectSlug}`)
}
export async function setWorkspacesEnabled(page: Page, projectSlug: string, enabled: boolean) {
@@ -553,11 +681,18 @@ export async function setWorkspacesEnabled(page: Page, projectSlug: string, enab
if (current === enabled) return
- await openProjectMenu(page, projectSlug)
+ const flip = async (timeout?: number) => {
+ const menu = await openProjectMenu(page, projectSlug)
+ const toggle = menu.locator(projectWorkspacesToggleSelector(projectSlug)).first()
+ await expect(toggle).toBeVisible()
+ return toggle.click({ force: true, timeout })
+ }
- const toggle = page.locator(projectWorkspacesToggleSelector(projectSlug)).first()
- await expect(toggle).toBeVisible()
- await toggle.click({ force: true })
+ const flipped = await flip(1500)
+ .then(() => true)
+ .catch(() => false)
+
+ if (!flipped) await flip()
const expected = enabled ? "New workspace" : "New session"
await expect(page.getByRole("button", { name: expected }).first()).toBeVisible()
diff --git a/packages/app/e2e/app/home.spec.ts b/packages/app/e2e/app/home.spec.ts
index f21dc40ec2..a3cedf7cb6 100644
--- a/packages/app/e2e/app/home.spec.ts
+++ b/packages/app/e2e/app/home.spec.ts
@@ -1,17 +1,17 @@
import { test, expect } from "../fixtures"
-import { serverName } from "../utils"
+import { serverNamePattern } from "../utils"
test("home renders and shows core entrypoints", async ({ page }) => {
await page.goto("/")
await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible()
- await expect(page.getByRole("button", { name: serverName })).toBeVisible()
+ await expect(page.getByRole("button", { name: serverNamePattern })).toBeVisible()
})
test("server picker dialog opens from home", async ({ page }) => {
await page.goto("/")
- const trigger = page.getByRole("button", { name: serverName })
+ const trigger = page.getByRole("button", { name: serverNamePattern })
await expect(trigger).toBeVisible()
await trigger.click()
diff --git a/packages/app/e2e/app/server-default.spec.ts b/packages/app/e2e/app/server-default.spec.ts
index adbc83473b..2c63130f67 100644
--- a/packages/app/e2e/app/server-default.spec.ts
+++ b/packages/app/e2e/app/server-default.spec.ts
@@ -1,6 +1,6 @@
import { test, expect } from "../fixtures"
-import { serverName, serverUrl } from "../utils"
-import { clickListItem, closeDialog, clickMenuItem } from "../actions"
+import { serverNamePattern, serverUrls } from "../utils"
+import { closeDialog, clickMenuItem } from "../actions"
const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl"
@@ -31,10 +31,9 @@ test("can set a default server on web", async ({ page, gotoSession }) => {
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
- const row = dialog.locator('[data-slot="list-item"]').filter({ hasText: serverName }).first()
- await expect(row).toBeVisible()
+ await expect(dialog.getByText(serverNamePattern).first()).toBeVisible()
- const menuTrigger = row.locator('[data-slot="dropdown-menu-trigger"]').first()
+ const menuTrigger = dialog.locator('[data-slot="dropdown-menu-trigger"]').first()
await expect(menuTrigger).toBeVisible()
await menuTrigger.click({ force: true })
@@ -42,14 +41,18 @@ test("can set a default server on web", async ({ page, gotoSession }) => {
await expect(menu).toBeVisible()
await clickMenuItem(menu, /set as default/i)
- await expect.poll(() => page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)).toBe(serverUrl)
- await expect(row.getByText("Default", { exact: true })).toBeVisible()
+ await expect
+ .poll(async () =>
+ serverUrls.includes((await page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)) ?? ""),
+ )
+ .toBe(true)
+ await expect(dialog.getByText("Default", { exact: true })).toBeVisible()
await closeDialog(page, dialog)
await ensurePopoverOpen()
- const serverRow = popover.locator("button").filter({ hasText: serverName }).first()
+ const serverRow = popover.locator("button").filter({ hasText: serverNamePattern }).first()
await expect(serverRow).toBeVisible()
await expect(serverRow.getByText("Default", { exact: true })).toBeVisible()
})
diff --git a/packages/app/e2e/app/titlebar-history.spec.ts b/packages/app/e2e/app/titlebar-history.spec.ts
index 9d6091176e..a4592ff1db 100644
--- a/packages/app/e2e/app/titlebar-history.spec.ts
+++ b/packages/app/e2e/app/titlebar-history.spec.ts
@@ -16,7 +16,6 @@ test("titlebar back/forward navigates between sessions", async ({ page, slug, sd
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
await expect(link).toBeVisible()
- await link.scrollIntoViewIfNeeded()
await link.click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
@@ -56,7 +55,6 @@ test("titlebar forward is cleared after branching history from sidebar", async (
const second = page.locator(`[data-session-id="${b.id}"] a`).first()
await expect(second).toBeVisible()
- await second.scrollIntoViewIfNeeded()
await second.click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${b.id}(?:\\?|#|$)`))
@@ -76,7 +74,6 @@ test("titlebar forward is cleared after branching history from sidebar", async (
const third = page.locator(`[data-session-id="${c.id}"] a`).first()
await expect(third).toBeVisible()
- await third.scrollIntoViewIfNeeded()
await third.click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${c.id}(?:\\?|#|$)`))
@@ -102,7 +99,6 @@ test("keyboard shortcuts navigate titlebar history", async ({ page, slug, sdk, g
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
await expect(link).toBeVisible()
- await link.scrollIntoViewIfNeeded()
await link.click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
diff --git a/packages/app/e2e/commands/panels.spec.ts b/packages/app/e2e/commands/panels.spec.ts
index 58c1f0a9af..7e5d7bd6e7 100644
--- a/packages/app/e2e/commands/panels.spec.ts
+++ b/packages/app/e2e/commands/panels.spec.ts
@@ -10,6 +10,8 @@ const expanded = async (el: { getAttribute: (name: string) => Promise {
await gotoSession()
+ const reviewPanel = page.locator("#review-panel")
+
const treeToggle = page.getByRole("button", { name: "Toggle file tree" }).first()
await expect(treeToggle).toBeVisible()
if (await expanded(treeToggle)) await treeToggle.click()
@@ -19,13 +21,13 @@ test("review panel can be toggled via keybind", async ({ page, gotoSession }) =>
await expect(reviewToggle).toBeVisible()
if (await expanded(reviewToggle)) await reviewToggle.click()
await expect(reviewToggle).toHaveAttribute("aria-expanded", "false")
- await expect(page.locator("#review-panel")).toHaveCount(0)
+ await expect(reviewPanel).toHaveAttribute("aria-hidden", "true")
await page.keyboard.press(`${modKey}+Shift+R`)
await expect(reviewToggle).toHaveAttribute("aria-expanded", "true")
- await expect(page.locator("#review-panel")).toBeVisible()
+ await expect(reviewPanel).toHaveAttribute("aria-hidden", "false")
await page.keyboard.press(`${modKey}+Shift+R`)
await expect(reviewToggle).toHaveAttribute("aria-expanded", "false")
- await expect(page.locator("#review-panel")).toHaveCount(0)
+ await expect(reviewPanel).toHaveAttribute("aria-hidden", "true")
})
diff --git a/packages/app/e2e/files/file-tree.spec.ts b/packages/app/e2e/files/file-tree.spec.ts
index 321d96af57..a5872bdf87 100644
--- a/packages/app/e2e/files/file-tree.spec.ts
+++ b/packages/app/e2e/files/file-tree.spec.ts
@@ -43,7 +43,14 @@ test("file tree can expand folders and open a file", async ({ page, gotoSession
await tab.click()
await expect(tab).toHaveAttribute("aria-selected", "true")
- const code = page.locator('[data-component="code"]').first()
- await expect(code).toBeVisible()
- await expect(code).toContainText("export default function FileTree")
+ await toggle.click()
+ await expect(toggle).toHaveAttribute("aria-expanded", "false")
+
+ await toggle.click()
+ await expect(toggle).toHaveAttribute("aria-expanded", "true")
+ await expect(allTab).toHaveAttribute("aria-selected", "true")
+
+ const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
+ await expect(viewer).toBeVisible()
+ await expect(viewer).toContainText("export default function FileTree")
})
diff --git a/packages/app/e2e/files/file-viewer.spec.ts b/packages/app/e2e/files/file-viewer.spec.ts
index b968acc130..49fe1baa13 100644
--- a/packages/app/e2e/files/file-viewer.spec.ts
+++ b/packages/app/e2e/files/file-viewer.spec.ts
@@ -1,5 +1,6 @@
import { test, expect } from "../fixtures"
import { promptSelector } from "../selectors"
+import { modKey } from "../utils"
test("smoke file viewer renders real file content", async ({ page, gotoSession }) => {
await gotoSession()
@@ -43,7 +44,113 @@ test("smoke file viewer renders real file content", async ({ page, gotoSession }
await expect(tab).toBeVisible()
await tab.click()
- const code = page.locator('[data-component="code"]').first()
- await expect(code).toBeVisible()
- await expect(code.getByText(/"name"\s*:\s*"@opencode-ai\/app"/)).toBeVisible()
+ const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
+ await expect(viewer).toBeVisible()
+ await expect(viewer.getByText(/"name"\s*:\s*"@opencode-ai\/app"/)).toBeVisible()
+})
+
+test("cmd+f opens text viewer search while prompt is focused", async ({ page, gotoSession }) => {
+ await gotoSession()
+
+ await page.locator(promptSelector).click()
+ await page.keyboard.type("/open")
+
+ const command = page.locator('[data-slash-id="file.open"]').first()
+ await expect(command).toBeVisible()
+ await page.keyboard.press("Enter")
+
+ const dialog = page
+ .getByRole("dialog")
+ .filter({ has: page.getByPlaceholder(/search files/i) })
+ .first()
+ await expect(dialog).toBeVisible()
+
+ const input = dialog.getByRole("textbox").first()
+ await input.fill("package.json")
+
+ const items = dialog.locator('[data-slot="list-item"][data-key^="file:"]')
+ let index = -1
+ await expect
+ .poll(
+ async () => {
+ const keys = await items.evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-key") ?? ""))
+ index = keys.findIndex((key) => /packages[\\/]+app[\\/]+package\.json$/i.test(key.replace(/^file:/, "")))
+ return index >= 0
+ },
+ { timeout: 30_000 },
+ )
+ .toBe(true)
+
+ const item = items.nth(index)
+ await expect(item).toBeVisible()
+ await item.click()
+
+ await expect(dialog).toHaveCount(0)
+
+ const tab = page.getByRole("tab", { name: "package.json" })
+ await expect(tab).toBeVisible()
+ await tab.click()
+
+ const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
+ await expect(viewer).toBeVisible()
+
+ await page.locator(promptSelector).click()
+ await page.keyboard.press(`${modKey}+f`)
+
+ const findInput = page.getByPlaceholder("Find")
+ await expect(findInput).toBeVisible()
+ await expect(findInput).toBeFocused()
+})
+
+test("cmd+f opens text viewer search while prompt is not focused", async ({ page, gotoSession }) => {
+ await gotoSession()
+
+ await page.locator(promptSelector).click()
+ await page.keyboard.type("/open")
+
+ const command = page.locator('[data-slash-id="file.open"]').first()
+ await expect(command).toBeVisible()
+ await page.keyboard.press("Enter")
+
+ const dialog = page
+ .getByRole("dialog")
+ .filter({ has: page.getByPlaceholder(/search files/i) })
+ .first()
+ await expect(dialog).toBeVisible()
+
+ const input = dialog.getByRole("textbox").first()
+ await input.fill("package.json")
+
+ const items = dialog.locator('[data-slot="list-item"][data-key^="file:"]')
+ let index = -1
+ await expect
+ .poll(
+ async () => {
+ const keys = await items.evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-key") ?? ""))
+ index = keys.findIndex((key) => /packages[\\/]+app[\\/]+package\.json$/i.test(key.replace(/^file:/, "")))
+ return index >= 0
+ },
+ { timeout: 30_000 },
+ )
+ .toBe(true)
+
+ const item = items.nth(index)
+ await expect(item).toBeVisible()
+ await item.click()
+
+ await expect(dialog).toHaveCount(0)
+
+ const tab = page.getByRole("tab", { name: "package.json" })
+ await expect(tab).toBeVisible()
+ await tab.click()
+
+ const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
+ await expect(viewer).toBeVisible()
+
+ await viewer.click()
+ await page.keyboard.press(`${modKey}+f`)
+
+ const findInput = page.getByPlaceholder("Find")
+ await expect(findInput).toBeVisible()
+ await expect(findInput).toBeFocused()
})
diff --git a/packages/app/e2e/fixtures.ts b/packages/app/e2e/fixtures.ts
index ea41ed8516..6a35c6901e 100644
--- a/packages/app/e2e/fixtures.ts
+++ b/packages/app/e2e/fixtures.ts
@@ -1,5 +1,5 @@
import { test as base, expect, type Page } from "@playwright/test"
-import { cleanupTestProject, createTestProject, seedProjects } from "./actions"
+import { cleanupSession, cleanupTestProject, createTestProject, seedProjects, sessionIDFromUrl } from "./actions"
import { promptSelector } from "./selectors"
import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils"
@@ -13,6 +13,8 @@ type TestFixtures = {
directory: string
slug: string
gotoSession: (sessionID?: string) => Promise
+ trackSession: (sessionID: string, directory?: string) => void
+ trackDirectory: (directory: string) => void
}) => Promise,
options?: { extra?: string[] },
) => Promise
@@ -51,20 +53,36 @@ export const test = base.extend({
},
withProject: async ({ page }, use) => {
await use(async (callback, options) => {
- const directory = await createTestProject()
- const slug = dirSlug(directory)
- await seedStorage(page, { directory, extra: options?.extra })
+ const root = await createTestProject()
+ const slug = dirSlug(root)
+ const sessions = new Map()
+ const dirs = new Set()
+ await seedStorage(page, { directory: root, extra: options?.extra })
const gotoSession = async (sessionID?: string) => {
- await page.goto(sessionPath(directory, sessionID))
+ await page.goto(sessionPath(root, sessionID))
await expect(page.locator(promptSelector)).toBeVisible()
+ const current = sessionIDFromUrl(page.url())
+ if (current) trackSession(current)
+ }
+
+ const trackSession = (sessionID: string, directory?: string) => {
+ sessions.set(sessionID, directory ?? root)
+ }
+
+ const trackDirectory = (directory: string) => {
+ if (directory !== root) dirs.add(directory)
}
try {
await gotoSession()
- return await callback({ directory, slug, gotoSession })
+ return await callback({ directory: root, slug, gotoSession, trackSession, trackDirectory })
} finally {
- await cleanupTestProject(directory)
+ await Promise.allSettled(
+ Array.from(sessions, ([sessionID, directory]) => cleanupSession({ sessionID, directory })),
+ )
+ await Promise.allSettled(Array.from(dirs, (directory) => cleanupTestProject(directory)))
+ await cleanupTestProject(root)
}
})
},
diff --git a/packages/app/e2e/projects/project-edit.spec.ts b/packages/app/e2e/projects/project-edit.spec.ts
index 4a286fea75..7c20f29ec1 100644
--- a/packages/app/e2e/projects/project-edit.spec.ts
+++ b/packages/app/e2e/projects/project-edit.spec.ts
@@ -1,25 +1,15 @@
import { test, expect } from "../fixtures"
-import { openSidebar } from "../actions"
+import { clickMenuItem, openProjectMenu, openSidebar } from "../actions"
test("dialog edit project updates name and startup script", async ({ page, withProject }) => {
await page.setViewportSize({ width: 1400, height: 800 })
- await withProject(async () => {
+ await withProject(async ({ slug }) => {
await openSidebar(page)
const open = async () => {
- const header = page.locator(".group\\/project").first()
- await header.hover()
- const trigger = header.getByRole("button", { name: "More options" }).first()
- await expect(trigger).toBeVisible()
- await trigger.click({ force: true })
-
- const menu = page.locator('[data-component="dropdown-menu-content"]').first()
- await expect(menu).toBeVisible()
-
- const editItem = menu.getByRole("menuitem", { name: "Edit" }).first()
- await expect(editItem).toBeVisible()
- await editItem.click({ force: true })
+ const menu = await openProjectMenu(page, slug)
+ await clickMenuItem(menu, /^Edit$/i, { force: true })
const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()
diff --git a/packages/app/e2e/projects/projects-close.spec.ts b/packages/app/e2e/projects/projects-close.spec.ts
index 4b39ed82c3..9454d683f0 100644
--- a/packages/app/e2e/projects/projects-close.spec.ts
+++ b/packages/app/e2e/projects/projects-close.spec.ts
@@ -1,36 +1,8 @@
import { test, expect } from "../fixtures"
import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem, openProjectMenu } from "../actions"
-import { projectCloseHoverSelector, projectSwitchSelector } from "../selectors"
+import { projectSwitchSelector } from "../selectors"
import { dirSlug } from "../utils"
-test("can close a project via hover card close button", async ({ page, withProject }) => {
- await page.setViewportSize({ width: 1400, height: 800 })
-
- const other = await createTestProject()
- const otherSlug = dirSlug(other)
-
- try {
- await withProject(
- async () => {
- await openSidebar(page)
-
- const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
- await expect(otherButton).toBeVisible()
- await otherButton.hover()
-
- const close = page.locator(projectCloseHoverSelector(otherSlug)).first()
- await expect(close).toBeVisible()
- await close.click()
-
- await expect(otherButton).toHaveCount(0)
- },
- { extra: [other] },
- )
- } finally {
- await cleanupTestProject(other)
- }
-})
-
test("closing active project navigates to another open project", async ({ page, withProject }) => {
await page.setViewportSize({ width: 1400, height: 800 })
@@ -53,16 +25,26 @@ test("closing active project navigates to another open project", async ({ page,
await clickMenuItem(menu, /^Close$/i, { force: true })
await expect
- .poll(() => {
- const pathname = new URL(page.url()).pathname
- if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
- if (pathname === "/") return "home"
- return ""
- })
+ .poll(
+ () => {
+ const pathname = new URL(page.url()).pathname
+ if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
+ if (pathname === "/") return "home"
+ return ""
+ },
+ { timeout: 15_000 },
+ )
.toMatch(/^(project|home)$/)
await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`))
- await expect(otherButton).toHaveCount(0)
+ await expect
+ .poll(
+ async () => {
+ return await page.locator(projectSwitchSelector(otherSlug)).count()
+ },
+ { timeout: 15_000 },
+ )
+ .toBe(0)
},
{ extra: [other] },
)
diff --git a/packages/app/e2e/projects/projects-switch.spec.ts b/packages/app/e2e/projects/projects-switch.spec.ts
index f17557a800..6ad64f5927 100644
--- a/packages/app/e2e/projects/projects-switch.spec.ts
+++ b/packages/app/e2e/projects/projects-switch.spec.ts
@@ -1,18 +1,39 @@
import { base64Decode } from "@opencode-ai/util/encode"
+import type { Page } from "@playwright/test"
import { test, expect } from "../fixtures"
-import {
- defocus,
- createTestProject,
- cleanupTestProject,
- openSidebar,
- setWorkspacesEnabled,
- sessionIDFromUrl,
-} from "../actions"
+import { defocus, createTestProject, cleanupTestProject, openSidebar, sessionIDFromUrl, waitSlug } from "../actions"
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
-import { createSdk, dirSlug } from "../utils"
+import { dirSlug, resolveDirectory } from "../utils"
-function slugFromUrl(url: string) {
- return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
+async function workspaces(page: Page, directory: string, enabled: boolean) {
+ await page.evaluate(
+ ({ directory, enabled }: { directory: string; enabled: boolean }) => {
+ const key = "opencode.global.dat:layout"
+ const raw = localStorage.getItem(key)
+ const data = raw ? JSON.parse(raw) : {}
+ const sidebar = data.sidebar && typeof data.sidebar === "object" ? data.sidebar : {}
+ const current =
+ sidebar.workspaces && typeof sidebar.workspaces === "object" && !Array.isArray(sidebar.workspaces)
+ ? sidebar.workspaces
+ : {}
+ const next = { ...current }
+
+ if (enabled) next[directory] = true
+ if (!enabled) delete next[directory]
+
+ localStorage.setItem(
+ key,
+ JSON.stringify({
+ ...data,
+ sidebar: {
+ ...sidebar,
+ workspaces: next,
+ },
+ }),
+ )
+ },
+ { directory, enabled },
+ )
}
test("can switch between projects from sidebar", async ({ page, withProject }) => {
@@ -51,57 +72,54 @@ 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
-
try {
await withProject(
- async ({ directory, slug }) => {
- rootDir = directory
+ async ({ directory, slug, trackSession, trackDirectory }) => {
await defocus(page)
+ await workspaces(page, directory, true)
+ await page.reload()
+ await expect(page.locator(promptSelector)).toBeVisible()
await openSidebar(page)
- await setWorkspacesEnabled(page, slug, true)
+ await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
await page.getByRole("button", { name: "New workspace" }).first().click()
- await expect
- .poll(
- () => {
- const next = slugFromUrl(page.url())
- if (!next) return ""
- if (next === slug) return ""
- return next
- },
- { timeout: 45_000 },
- )
- .not.toBe("")
-
- const workspaceSlug = slugFromUrl(page.url())
- workspaceDir = base64Decode(workspaceSlug)
+ const raw = await waitSlug(page, [slug])
+ const dir = base64Decode(raw)
+ if (!dir) throw new Error(`Failed to decode workspace slug: ${raw}`)
+ const space = await resolveDirectory(dir)
+ const next = dirSlug(space)
+ trackDirectory(space)
await openSidebar(page)
- const workspace = page.locator(workspaceItemSelector(workspaceSlug)).first()
- await expect(workspace).toBeVisible()
- await workspace.hover()
+ const item = page.locator(`${workspaceItemSelector(next)}, ${workspaceItemSelector(raw)}`).first()
+ await expect(item).toBeVisible()
+ await item.hover()
- const newSession = page.locator(workspaceNewSessionSelector(workspaceSlug)).first()
- await expect(newSession).toBeVisible()
- await newSession.click({ force: true })
+ const btn = page.locator(`${workspaceNewSessionSelector(next)}, ${workspaceNewSessionSelector(raw)}`).first()
+ await expect(btn).toBeVisible()
+ await btn.click({ force: true })
- await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`))
+ // A new workspace can be discovered via a transient slug before the route and sidebar
+ // settle to the canonical workspace path on Windows, so interact with either and assert
+ // against the resolved workspace slug.
+ await waitSlug(page)
+ await expect(page).toHaveURL(new RegExp(`/${next}/session(?:[/?#]|$)`))
+ // Create a session by sending a prompt
const prompt = page.locator(promptSelector)
await expect(prompt).toBeVisible()
- await prompt.fill(`project switch remembers workspace ${stamp}`)
- await prompt.press("Enter")
+ await prompt.fill("test")
+ await page.keyboard.press("Enter")
+
+ // Wait for the URL to update with the new session ID
+ await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("")
- 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()}`)
- sessionID = created
- await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
+ if (!created) throw new Error(`Failed to get session ID from url: ${page.url()}`)
+ trackSession(created, space)
+
+ await expect(page).toHaveURL(new RegExp(`/${next}/session/${created}(?:[/?#]|$)`))
await openSidebar(page)
@@ -114,25 +132,12 @@ 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] },
)
} finally {
- if (sessionID) {
- const id = sessionID
- const dirs = [rootDir, workspaceDir].filter((x): x is string => !!x)
- await Promise.all(
- dirs.map((directory) =>
- createSdk(directory)
- .session.delete({ sessionID: id })
- .catch(() => undefined),
- ),
- )
- }
- if (workspaceDir) {
- await cleanupTestProject(workspaceDir)
- }
await cleanupTestProject(other)
}
})
diff --git a/packages/app/e2e/projects/workspace-new-session.spec.ts b/packages/app/e2e/projects/workspace-new-session.spec.ts
index f33972cc3a..18fa46d329 100644
--- a/packages/app/e2e/projects/workspace-new-session.spec.ts
+++ b/packages/app/e2e/projects/workspace-new-session.spec.ts
@@ -1,14 +1,10 @@
import { base64Decode } from "@opencode-ai/util/encode"
import type { Page } from "@playwright/test"
import { test, expect } from "../fixtures"
-import { cleanupTestProject, openSidebar, sessionIDFromUrl, setWorkspacesEnabled } from "../actions"
+import { openSidebar, sessionIDFromUrl, setWorkspacesEnabled, slugFromUrl, waitSlug } from "../actions"
import { promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
import { createSdk } from "../utils"
-function slugFromUrl(url: string) {
- return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
-}
-
async function waitWorkspaceReady(page: Page, slug: string) {
await openSidebar(page)
await expect
@@ -31,20 +27,7 @@ async function createWorkspace(page: Page, root: string, seen: string[]) {
await openSidebar(page)
await page.getByRole("button", { name: "New workspace" }).first().click()
- await expect
- .poll(
- () => {
- const slug = slugFromUrl(page.url())
- if (!slug) return ""
- if (slug === root) return ""
- if (seen.includes(slug)) return ""
- return slug
- },
- { timeout: 45_000 },
- )
- .not.toBe("")
-
- const slug = slugFromUrl(page.url())
+ const slug = await waitSlug(page, [root, ...seen])
const directory = base64Decode(slug)
if (!directory) throw new Error(`Failed to decode workspace slug: ${slug}`)
return { slug, directory }
@@ -60,12 +43,13 @@ async function openWorkspaceNewSession(page: Page, slug: string) {
await expect(button).toBeVisible()
await button.click({ force: true })
- await expect.poll(() => slugFromUrl(page.url())).toBe(slug)
- await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:[/?#]|$)`))
+ const next = await waitSlug(page)
+ await expect(page).toHaveURL(new RegExp(`/${next}/session(?:[/?#]|$)`))
+ return next
}
async function createSessionFromWorkspace(page: Page, slug: string, text: string) {
- await openWorkspaceNewSession(page, slug)
+ const next = await openWorkspaceNewSession(page, slug)
const prompt = page.locator(promptSelector)
await expect(prompt).toBeVisible()
@@ -76,13 +60,13 @@ async function createSessionFromWorkspace(page: Page, slug: string, text: string
await expect.poll(async () => ((await prompt.textContent()) ?? "").trim()).toContain(text)
await prompt.press("Enter")
- await expect.poll(() => slugFromUrl(page.url())).toBe(slug)
+ await expect.poll(() => slugFromUrl(page.url())).toBe(next)
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
const sessionID = sessionIDFromUrl(page.url())
if (!sessionID) throw new Error(`Failed to parse session id from url: ${page.url()}`)
- await expect(page).toHaveURL(new RegExp(`/${slug}/session/${sessionID}(?:[/?#]|$)`))
- return sessionID
+ await expect(page).toHaveURL(new RegExp(`/${next}/session/${sessionID}(?:[/?#]|$)`))
+ return { sessionID, slug: next }
}
async function sessionDirectory(directory: string, sessionID: string) {
@@ -97,48 +81,29 @@ async function sessionDirectory(directory: string, sessionID: string) {
test("new sessions from sidebar workspace actions stay in selected workspace", async ({ page, withProject }) => {
await page.setViewportSize({ width: 1400, height: 800 })
- await withProject(async ({ directory, slug: root }) => {
- const workspaces = [] as { slug: string; directory: string }[]
- const sessions = [] as string[]
+ await withProject(async ({ directory, slug: root, trackSession, trackDirectory }) => {
+ await openSidebar(page)
+ await setWorkspacesEnabled(page, root, true)
- try {
- await openSidebar(page)
- await setWorkspacesEnabled(page, root, true)
+ const first = await createWorkspace(page, root, [])
+ trackDirectory(first.directory)
+ await waitWorkspaceReady(page, first.slug)
- const first = await createWorkspace(page, root, [])
- workspaces.push(first)
- await waitWorkspaceReady(page, first.slug)
+ const second = await createWorkspace(page, root, [first.slug])
+ trackDirectory(second.directory)
+ await waitWorkspaceReady(page, second.slug)
- const second = await createWorkspace(page, root, [first.slug])
- workspaces.push(second)
- await waitWorkspaceReady(page, second.slug)
+ const firstSession = await createSessionFromWorkspace(page, first.slug, `workspace one ${Date.now()}`)
+ trackSession(firstSession.sessionID, first.directory)
- const firstSession = await createSessionFromWorkspace(page, first.slug, `workspace one ${Date.now()}`)
- sessions.push(firstSession)
+ const secondSession = await createSessionFromWorkspace(page, second.slug, `workspace two ${Date.now()}`)
+ trackSession(secondSession.sessionID, second.directory)
- const secondSession = await createSessionFromWorkspace(page, second.slug, `workspace two ${Date.now()}`)
- sessions.push(secondSession)
+ const thirdSession = await createSessionFromWorkspace(page, first.slug, `workspace one again ${Date.now()}`)
+ trackSession(thirdSession.sessionID, first.directory)
- const thirdSession = await createSessionFromWorkspace(page, first.slug, `workspace one again ${Date.now()}`)
- sessions.push(thirdSession)
-
- await expect.poll(() => sessionDirectory(first.directory, firstSession)).toBe(first.directory)
- await expect.poll(() => sessionDirectory(second.directory, secondSession)).toBe(second.directory)
- await expect.poll(() => sessionDirectory(first.directory, thirdSession)).toBe(first.directory)
- } finally {
- const dirs = [directory, ...workspaces.map((workspace) => workspace.directory)]
- await Promise.all(
- sessions.map((sessionID) =>
- Promise.all(
- dirs.map((dir) =>
- createSdk(dir)
- .session.delete({ sessionID })
- .catch(() => undefined),
- ),
- ),
- ),
- )
- await Promise.all(workspaces.map((workspace) => cleanupTestProject(workspace.directory)))
- }
+ await expect.poll(() => sessionDirectory(first.directory, firstSession.sessionID)).toBe(first.directory)
+ await expect.poll(() => sessionDirectory(second.directory, secondSession.sessionID)).toBe(second.directory)
+ await expect.poll(() => sessionDirectory(first.directory, thirdSession.sessionID)).toBe(first.directory)
})
})
diff --git a/packages/app/e2e/projects/workspaces.spec.ts b/packages/app/e2e/projects/workspaces.spec.ts
index 3867395267..aeeccb9bba 100644
--- a/packages/app/e2e/projects/workspaces.spec.ts
+++ b/packages/app/e2e/projects/workspaces.spec.ts
@@ -14,14 +14,12 @@ import {
openSidebar,
openWorkspaceMenu,
setWorkspacesEnabled,
+ slugFromUrl,
+ waitSlug,
} from "../actions"
import { dropdownMenuContentSelector, inlineInputSelector, workspaceItemSelector } from "../selectors"
import { createSdk, dirSlug } from "../utils"
-function slugFromUrl(url: string) {
- return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
-}
-
async function setupWorkspaceTest(page: Page, project: { slug: string }) {
const rootSlug = project.slug
await openSidebar(page)
@@ -29,17 +27,7 @@ async function setupWorkspaceTest(page: Page, project: { slug: string }) {
await setWorkspacesEnabled(page, rootSlug, true)
await page.getByRole("button", { name: "New workspace" }).first().click()
- await expect
- .poll(
- () => {
- const slug = slugFromUrl(page.url())
- return slug.length > 0 && slug !== rootSlug
- },
- { timeout: 45_000 },
- )
- .toBe(true)
-
- const slug = slugFromUrl(page.url())
+ const slug = await waitSlug(page, [rootSlug])
const dir = base64Decode(slug)
await openSidebar(page)
@@ -91,18 +79,7 @@ test("can create a workspace", async ({ page, withProject }) => {
await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
await page.getByRole("button", { name: "New workspace" }).first().click()
-
- await expect
- .poll(
- () => {
- const currentSlug = slugFromUrl(page.url())
- return currentSlug.length > 0 && currentSlug !== slug
- },
- { timeout: 45_000 },
- )
- .toBe(true)
-
- const workspaceSlug = slugFromUrl(page.url())
+ const workspaceSlug = await waitSlug(page, [slug])
const workspaceDir = base64Decode(workspaceSlug)
await openSidebar(page)
@@ -279,7 +256,7 @@ test("can delete a workspace", async ({ page, withProject }) => {
await clickMenuItem(menu, /^Delete$/i, { force: true })
await confirmDialog(page, /^Delete workspace$/i)
- await expect(page).toHaveURL(new RegExp(`/${rootSlug}/session`))
+ await expect.poll(() => base64Decode(slugFromUrl(page.url()))).toBe(project.directory)
await expect
.poll(
@@ -336,9 +313,6 @@ test("can reorder workspaces by drag and drop", async ({ page, withProject }) =>
const src = page.locator(workspaceItemSelector(from)).first()
const dst = page.locator(workspaceItemSelector(to)).first()
- await src.scrollIntoViewIfNeeded()
- await dst.scrollIntoViewIfNeeded()
-
const a = await src.boundingBox()
const b = await dst.boundingBox()
if (!a || !b) throw new Error("Failed to resolve workspace drag bounds")
@@ -357,17 +331,7 @@ test("can reorder workspaces by drag and drop", async ({ page, withProject }) =>
for (const _ of [0, 1]) {
const prev = slugFromUrl(page.url())
await page.getByRole("button", { name: "New workspace" }).first().click()
- await expect
- .poll(
- () => {
- const slug = slugFromUrl(page.url())
- return slug.length > 0 && slug !== rootSlug && slug !== prev
- },
- { timeout: 45_000 },
- )
- .toBe(true)
-
- const slug = slugFromUrl(page.url())
+ const slug = await waitSlug(page, [rootSlug, prev])
const dir = base64Decode(slug)
workspaces.push({ slug, directory: dir })
diff --git a/packages/app/e2e/prompt/prompt-async.spec.ts b/packages/app/e2e/prompt/prompt-async.spec.ts
index ce9b1a7a3b..51fbc3e4ae 100644
--- a/packages/app/e2e/prompt/prompt-async.spec.ts
+++ b/packages/app/e2e/prompt/prompt-async.spec.ts
@@ -1,6 +1,8 @@
import { test, expect } from "../fixtures"
import { promptSelector } from "../selectors"
-import { sessionIDFromUrl } from "../actions"
+import { cleanupSession, sessionIDFromUrl, withSession } from "../actions"
+
+const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
// Regression test for Issue #12453: the synchronous POST /message endpoint holds
// the connection open while the agent works, causing "Failed to fetch" over
@@ -38,6 +40,37 @@ test("prompt succeeds when sync message endpoint is unreachable", async ({ page,
)
.toContain(token)
} finally {
- await sdk.session.delete({ sessionID }).catch(() => undefined)
+ await cleanupSession({ sdk, sessionID })
}
})
+
+test("failed prompt send restores the composer input", async ({ page, sdk, gotoSession }) => {
+ await withSession(sdk, `e2e prompt failure ${Date.now()}`, async (session) => {
+ const prompt = page.locator(promptSelector)
+ const value = `restore ${Date.now()}`
+
+ await page.route(`**/session/${session.id}/prompt_async`, (route) =>
+ route.fulfill({
+ status: 500,
+ contentType: "application/json",
+ body: JSON.stringify({ message: "e2e prompt failure" }),
+ }),
+ )
+
+ await gotoSession(session.id)
+ await prompt.click()
+ await page.keyboard.type(value)
+ await page.keyboard.press("Enter")
+
+ await expect.poll(async () => text(await prompt.textContent())).toBe(value)
+ await expect
+ .poll(
+ async () => {
+ const messages = await sdk.session.messages({ sessionID: session.id, limit: 50 }).then((r) => r.data ?? [])
+ return messages.length
+ },
+ { timeout: 15_000 },
+ )
+ .toBe(0)
+ })
+})
diff --git a/packages/app/e2e/prompt/prompt-history.spec.ts b/packages/app/e2e/prompt/prompt-history.spec.ts
new file mode 100644
index 0000000000..ec68998144
--- /dev/null
+++ b/packages/app/e2e/prompt/prompt-history.spec.ts
@@ -0,0 +1,181 @@
+import type { ToolPart } from "@opencode-ai/sdk/v2/client"
+import type { Page } from "@playwright/test"
+import { test, expect } from "../fixtures"
+import { withSession } from "../actions"
+import { promptSelector } from "../selectors"
+
+const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
+
+const isBash = (part: unknown): part is ToolPart => {
+ if (!part || typeof part !== "object") return false
+ if (!("type" in part) || part.type !== "tool") return false
+ if (!("tool" in part) || part.tool !== "bash") return false
+ return "state" in part
+}
+
+async function edge(page: Page, pos: "start" | "end") {
+ await page.locator(promptSelector).evaluate((el: HTMLDivElement, pos: "start" | "end") => {
+ const selection = window.getSelection()
+ if (!selection) return
+
+ const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
+ const nodes: Text[] = []
+ for (let node = walk.nextNode(); node; node = walk.nextNode()) {
+ nodes.push(node as Text)
+ }
+
+ if (nodes.length === 0) {
+ const node = document.createTextNode("")
+ el.appendChild(node)
+ nodes.push(node)
+ }
+
+ const node = pos === "start" ? nodes[0]! : nodes[nodes.length - 1]!
+ const range = document.createRange()
+ range.setStart(node, pos === "start" ? 0 : (node.textContent ?? "").length)
+ range.collapse(true)
+ selection.removeAllRanges()
+ selection.addRange(range)
+ }, pos)
+}
+
+async function wait(page: Page, value: string) {
+ await expect.poll(async () => text(await page.locator(promptSelector).textContent())).toBe(value)
+}
+
+async function reply(sdk: Parameters[0], sessionID: string, token: string) {
+ await expect
+ .poll(
+ async () => {
+ const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
+ return messages
+ .filter((item) => item.info.role === "assistant")
+ .flatMap((item) => item.parts)
+ .filter((item) => item.type === "text")
+ .map((item) => item.text)
+ .join("\n")
+ },
+ { timeout: 90_000 },
+ )
+ .toContain(token)
+}
+
+async function shell(sdk: Parameters[0], sessionID: string, cmd: string, token: string) {
+ await expect
+ .poll(
+ async () => {
+ const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
+ const part = messages
+ .filter((item) => item.info.role === "assistant")
+ .flatMap((item) => item.parts)
+ .filter(isBash)
+ .find((item) => item.state.input?.command === cmd && item.state.status === "completed")
+
+ if (!part || part.state.status !== "completed") return
+ return typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
+ },
+ { timeout: 90_000 },
+ )
+ .toContain(token)
+}
+
+test("prompt history restores unsent draft with arrow navigation", async ({ page, sdk, gotoSession }) => {
+ test.setTimeout(120_000)
+
+ await withSession(sdk, `e2e prompt history ${Date.now()}`, async (session) => {
+ await gotoSession(session.id)
+
+ const prompt = page.locator(promptSelector)
+ const firstToken = `E2E_HISTORY_ONE_${Date.now()}`
+ const secondToken = `E2E_HISTORY_TWO_${Date.now()}`
+ const first = `Reply with exactly: ${firstToken}`
+ const second = `Reply with exactly: ${secondToken}`
+ const draft = `draft ${Date.now()}`
+
+ await prompt.click()
+ await page.keyboard.type(first)
+ await page.keyboard.press("Enter")
+ await wait(page, "")
+ await reply(sdk, session.id, firstToken)
+
+ await prompt.click()
+ await page.keyboard.type(second)
+ await page.keyboard.press("Enter")
+ await wait(page, "")
+ await reply(sdk, session.id, secondToken)
+
+ await prompt.click()
+ await page.keyboard.type(draft)
+ await wait(page, draft)
+
+ await edge(page, "start")
+ await page.keyboard.press("ArrowUp")
+ await wait(page, second)
+
+ await page.keyboard.press("ArrowUp")
+ await wait(page, first)
+
+ await page.keyboard.press("ArrowDown")
+ await wait(page, second)
+
+ await page.keyboard.press("ArrowDown")
+ await wait(page, draft)
+ })
+})
+
+test("shell history stays separate from normal prompt history", async ({ page, sdk, gotoSession }) => {
+ test.setTimeout(120_000)
+
+ await withSession(sdk, `e2e shell history ${Date.now()}`, async (session) => {
+ await gotoSession(session.id)
+
+ const prompt = page.locator(promptSelector)
+ const firstToken = `E2E_SHELL_ONE_${Date.now()}`
+ const secondToken = `E2E_SHELL_TWO_${Date.now()}`
+ const normalToken = `E2E_NORMAL_${Date.now()}`
+ const first = `echo ${firstToken}`
+ const second = `echo ${secondToken}`
+ const normal = `Reply with exactly: ${normalToken}`
+
+ await prompt.click()
+ await page.keyboard.type("!")
+ await page.keyboard.type(first)
+ await page.keyboard.press("Enter")
+ await wait(page, "")
+ await shell(sdk, session.id, first, firstToken)
+
+ await prompt.click()
+ await page.keyboard.type("!")
+ await page.keyboard.type(second)
+ await page.keyboard.press("Enter")
+ await wait(page, "")
+ await shell(sdk, session.id, second, secondToken)
+
+ await prompt.click()
+ await page.keyboard.type("!")
+ await page.keyboard.press("ArrowUp")
+ await wait(page, second)
+
+ await page.keyboard.press("ArrowUp")
+ await wait(page, first)
+
+ await page.keyboard.press("ArrowDown")
+ await wait(page, second)
+
+ await page.keyboard.press("ArrowDown")
+ await wait(page, "")
+
+ await page.keyboard.press("Escape")
+ await wait(page, "")
+
+ await prompt.click()
+ await page.keyboard.type(normal)
+ await page.keyboard.press("Enter")
+ await wait(page, "")
+ await reply(sdk, session.id, normalToken)
+
+ await prompt.click()
+ await page.keyboard.press("ArrowUp")
+ await wait(page, normal)
+ })
+})
diff --git a/packages/app/e2e/prompt/prompt-shell.spec.ts b/packages/app/e2e/prompt/prompt-shell.spec.ts
new file mode 100644
index 0000000000..4c92f4a2f2
--- /dev/null
+++ b/packages/app/e2e/prompt/prompt-shell.spec.ts
@@ -0,0 +1,62 @@
+import type { ToolPart } from "@opencode-ai/sdk/v2/client"
+import { test, expect } from "../fixtures"
+import { sessionIDFromUrl } from "../actions"
+import { promptSelector } from "../selectors"
+import { createSdk } from "../utils"
+
+const isBash = (part: unknown): part is ToolPart => {
+ if (!part || typeof part !== "object") return false
+ if (!("type" in part) || part.type !== "tool") return false
+ if (!("tool" in part) || part.tool !== "bash") return false
+ return "state" in part
+}
+
+test("shell mode runs a command in the project directory", async ({ page, withProject }) => {
+ test.setTimeout(120_000)
+
+ await withProject(async ({ directory, gotoSession, trackSession }) => {
+ const sdk = createSdk(directory)
+ const prompt = page.locator(promptSelector)
+ const cmd = process.platform === "win32" ? "dir" : "ls"
+
+ await gotoSession()
+ await prompt.click()
+ await page.keyboard.type("!")
+ await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
+
+ await page.keyboard.type(cmd)
+ await page.keyboard.press("Enter")
+
+ await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
+
+ const id = sessionIDFromUrl(page.url())
+ if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
+ trackSession(id, directory)
+
+ await expect
+ .poll(
+ async () => {
+ const list = await sdk.session.messages({ sessionID: id, limit: 50 }).then((x) => x.data ?? [])
+ const msg = list.findLast(
+ (item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === directory,
+ )
+ if (!msg) return
+
+ const part = msg.parts
+ .filter(isBash)
+ .find((item) => item.state.input?.command === cmd && item.state.status === "completed")
+
+ if (!part || part.state.status !== "completed") return
+ const output =
+ typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
+ if (!output.includes("README.md")) return
+
+ return { cwd: directory, output }
+ },
+ { timeout: 90_000 },
+ )
+ .toEqual(expect.objectContaining({ cwd: directory, output: expect.stringContaining("README.md") }))
+
+ await expect(prompt).toHaveText("")
+ })
+})
diff --git a/packages/app/e2e/prompt/prompt-slash-share.spec.ts b/packages/app/e2e/prompt/prompt-slash-share.spec.ts
new file mode 100644
index 0000000000..817b353a7c
--- /dev/null
+++ b/packages/app/e2e/prompt/prompt-slash-share.spec.ts
@@ -0,0 +1,64 @@
+import { test, expect } from "../fixtures"
+import { promptSelector } from "../selectors"
+import { withSession } from "../actions"
+
+const shareDisabled = process.env.OPENCODE_DISABLE_SHARE === "true" || process.env.OPENCODE_DISABLE_SHARE === "1"
+
+async function seed(sdk: Parameters[0], sessionID: string) {
+ await sdk.session.promptAsync({
+ sessionID,
+ noReply: true,
+ parts: [{ type: "text", text: "e2e share seed" }],
+ })
+
+ await expect
+ .poll(
+ async () => {
+ const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
+ return messages.length
+ },
+ { timeout: 30_000 },
+ )
+ .toBeGreaterThan(0)
+}
+
+test("/share and /unshare update session share state", async ({ page, sdk, gotoSession }) => {
+ test.skip(shareDisabled, "Share is disabled in this environment (OPENCODE_DISABLE_SHARE).")
+
+ await withSession(sdk, `e2e slash share ${Date.now()}`, async (session) => {
+ const prompt = page.locator(promptSelector)
+
+ await seed(sdk, session.id)
+ await gotoSession(session.id)
+
+ await prompt.click()
+ await page.keyboard.type("/share")
+ await expect(page.locator('[data-slash-id="session.share"]').first()).toBeVisible()
+ await page.keyboard.press("Enter")
+
+ await expect
+ .poll(
+ async () => {
+ const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
+ return data?.share?.url || undefined
+ },
+ { timeout: 30_000 },
+ )
+ .not.toBeUndefined()
+
+ await prompt.click()
+ await page.keyboard.type("/unshare")
+ await expect(page.locator('[data-slash-id="session.unshare"]').first()).toBeVisible()
+ await page.keyboard.press("Enter")
+
+ await expect
+ .poll(
+ async () => {
+ const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
+ return data?.share?.url || undefined
+ },
+ { timeout: 30_000 },
+ )
+ .toBeUndefined()
+ })
+})
diff --git a/packages/app/e2e/prompt/prompt.spec.ts b/packages/app/e2e/prompt/prompt.spec.ts
index ff9f5daf0d..0466d0988c 100644
--- a/packages/app/e2e/prompt/prompt.spec.ts
+++ b/packages/app/e2e/prompt/prompt.spec.ts
@@ -1,6 +1,6 @@
import { test, expect } from "../fixtures"
import { promptSelector } from "../selectors"
-import { sessionIDFromUrl, withSession } from "../actions"
+import { cleanupSession, sessionIDFromUrl, withSession } from "../actions"
test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) => {
test.setTimeout(120_000)
@@ -46,7 +46,7 @@ test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession })
.toContain(token)
} finally {
page.off("pageerror", onPageError)
- await sdk.session.delete({ sessionID }).catch(() => undefined)
+ await cleanupSession({ sdk, sessionID })
}
if (pageErrors.length > 0) {
diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts
index be0bc05717..2061a11284 100644
--- a/packages/app/e2e/selectors.ts
+++ b/packages/app/e2e/selectors.ts
@@ -20,11 +20,8 @@ export const settingsNotificationsAgentSelector = '[data-action="settings-notifi
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
export const settingsSoundsAgentSelector = '[data-action="settings-sounds-agent"]'
-export const settingsSoundsAgentEnabledSelector = '[data-action="settings-sounds-agent-enabled"]'
export const settingsSoundsPermissionsSelector = '[data-action="settings-sounds-permissions"]'
-export const settingsSoundsPermissionsEnabledSelector = '[data-action="settings-sounds-permissions-enabled"]'
export const settingsSoundsErrorsSelector = '[data-action="settings-sounds-errors"]'
-export const settingsSoundsErrorsEnabledSelector = '[data-action="settings-sounds-errors-enabled"]'
export const settingsUpdatesStartupSelector = '[data-action="settings-updates-startup"]'
export const settingsReleaseNotesSelector = '[data-action="settings-release-notes"]'
@@ -33,8 +30,6 @@ export const sidebarNavSelector = '[data-component="sidebar-nav-desktop"]'
export const projectSwitchSelector = (slug: string) =>
`${sidebarNavSelector} [data-action="project-switch"][data-project="${slug}"]`
-export const projectCloseHoverSelector = (slug: string) => `[data-action="project-close-hover"][data-project="${slug}"]`
-
export const projectMenuTriggerSelector = (slug: string) =>
`${sidebarNavSelector} [data-action="project-menu"][data-project="${slug}"]`
diff --git a/packages/app/e2e/session/session-child-navigation.spec.ts b/packages/app/e2e/session/session-child-navigation.spec.ts
new file mode 100644
index 0000000000..ac2dca33c8
--- /dev/null
+++ b/packages/app/e2e/session/session-child-navigation.spec.ts
@@ -0,0 +1,37 @@
+import { seedSessionTask, withSession } from "../actions"
+import { test, expect } from "../fixtures"
+
+test("task tool child-session link does not trigger stale show errors", async ({ page, sdk, gotoSession }) => {
+ test.setTimeout(120_000)
+
+ const errs: string[] = []
+ const onError = (err: Error) => {
+ errs.push(err.message)
+ }
+ page.on("pageerror", onError)
+
+ await withSession(sdk, `e2e child nav ${Date.now()}`, async (session) => {
+ const child = await seedSessionTask(sdk, {
+ sessionID: session.id,
+ description: "Open child session",
+ prompt: "Search the repository for AssistantParts and then reply with exactly CHILD_OK.",
+ })
+
+ try {
+ await gotoSession(session.id)
+
+ const link = page
+ .locator("a.subagent-link")
+ .filter({ hasText: /open child session/i })
+ .first()
+ await expect(link).toBeVisible({ timeout: 30_000 })
+ await link.click()
+
+ await expect(page).toHaveURL(new RegExp(`/session/${child.sessionID}(?:[/?#]|$)`), { timeout: 30_000 })
+ await page.waitForTimeout(1000)
+ expect(errs).toEqual([])
+ } finally {
+ page.off("pageerror", onError)
+ }
+ })
+})
diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts
index 6bf7714a66..055e8eed29 100644
--- a/packages/app/e2e/session/session-composer-dock.spec.ts
+++ b/packages/app/e2e/session/session-composer-dock.spec.ts
@@ -1,5 +1,5 @@
import { test, expect } from "../fixtures"
-import { clearSessionDockSeed, seedSessionPermission, seedSessionQuestion, seedSessionTodos } from "../actions"
+import { cleanupSession, clearSessionDockSeed, seedSessionQuestion, seedSessionTodos } from "../actions"
import {
permissionDockSelector,
promptSelector,
@@ -11,11 +11,23 @@ import {
} from "../selectors"
type Sdk = Parameters[0]
+type PermissionRule = { permission: string; pattern: string; action: "allow" | "deny" | "ask" }
-async function withDockSession(sdk: Sdk, title: string, fn: (session: { id: string; title: string }) => Promise) {
- const session = await sdk.session.create({ title }).then((r) => r.data)
+async function withDockSession(
+ sdk: Sdk,
+ title: string,
+ fn: (session: { id: string; title: string }) => Promise,
+ opts?: { permission?: PermissionRule[] },
+) {
+ const session = await sdk.session
+ .create(opts?.permission ? { title, permission: opts.permission } : { title })
+ .then((r) => r.data)
if (!session?.id) throw new Error("Session create did not return an id")
- return fn(session)
+ try {
+ return await fn(session)
+ } finally {
+ await cleanupSession({ sdk, sessionID: session.id })
+ }
}
test.setTimeout(120_000)
@@ -28,6 +40,94 @@ async function withDockSeed(sdk: Sdk, sessionID: string, fn: () => Promise
}
}
+async function clearPermissionDock(page: any, label: RegExp) {
+ const dock = page.locator(permissionDockSelector)
+ for (let i = 0; i < 3; i++) {
+ const count = await dock.count()
+ if (count === 0) return
+ await dock.getByRole("button", { name: label }).click()
+ await page.waitForTimeout(150)
+ }
+}
+
+async function setAutoAccept(page: any, enabled: boolean) {
+ const button = page.locator('[data-action="prompt-permissions"]').first()
+ await expect(button).toBeVisible()
+ const pressed = (await button.getAttribute("aria-pressed")) === "true"
+ if (pressed === enabled) return
+ await button.click()
+ await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false")
+}
+
+async function withMockPermission(
+ page: any,
+ request: {
+ id: string
+ sessionID: string
+ permission: string
+ patterns: string[]
+ metadata?: Record
+ always?: string[]
+ },
+ opts: { child?: any } | undefined,
+ fn: () => Promise,
+) {
+ let pending = [
+ {
+ ...request,
+ always: request.always ?? ["*"],
+ metadata: request.metadata ?? {},
+ },
+ ]
+
+ const list = async (route: any) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(pending),
+ })
+ }
+
+ const reply = async (route: any) => {
+ const url = new URL(route.request().url())
+ const id = url.pathname.split("/").pop()
+ pending = pending.filter((item) => item.id !== id)
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(true),
+ })
+ }
+
+ await page.route("**/permission", list)
+ await page.route("**/session/*/permissions/*", reply)
+
+ const sessionList = opts?.child
+ ? async (route: any) => {
+ const res = await route.fetch()
+ const json = await res.json()
+ const list = Array.isArray(json) ? json : Array.isArray(json?.data) ? json.data : undefined
+ if (Array.isArray(list) && !list.some((item) => item?.id === opts.child?.id)) list.push(opts.child)
+ await route.fulfill({
+ status: res.status(),
+ headers: res.headers(),
+ contentType: "application/json",
+ body: JSON.stringify(json),
+ })
+ }
+ : undefined
+
+ if (sessionList) await page.route("**/session?*", sessionList)
+
+ try {
+ return await fn()
+ } finally {
+ await page.unroute("**/permission", list)
+ await page.unroute("**/session/*/permissions/*", reply)
+ if (sessionList) await page.unroute("**/session?*", sessionList)
+ }
+}
+
test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock default", async (session) => {
await gotoSession(session.id)
@@ -42,6 +142,17 @@ test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
})
})
+test("auto-accept toggle works before first submit", async ({ page, gotoSession }) => {
+ await gotoSession()
+
+ const button = page.locator('[data-action="prompt-permissions"]').first()
+ await expect(button).toBeVisible()
+ await expect(button).toHaveAttribute("aria-pressed", "false")
+
+ await setAutoAccept(page, true)
+ await setAutoAccept(page, false)
+})
+
test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock question", async (session) => {
await withDockSeed(sdk, session.id, async () => {
@@ -76,72 +187,179 @@ test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSess
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
- await withDockSeed(sdk, session.id, async () => {
- await gotoSession(session.id)
-
- await seedSessionPermission(sdk, {
+ await gotoSession(session.id)
+ await setAutoAccept(page, false)
+ await withMockPermission(
+ page,
+ {
+ id: "per_e2e_once",
sessionID: session.id,
permission: "bash",
- patterns: ["README.md"],
- description: "Need permission for command",
- })
+ patterns: ["/tmp/opencode-e2e-perm-once"],
+ metadata: { description: "Need permission for command" },
+ },
+ undefined,
+ async () => {
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
+ await expect(page.locator(promptSelector)).toHaveCount(0)
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
- await expect(page.locator(promptSelector)).toHaveCount(0)
-
- await page
- .locator(permissionDockSelector)
- .getByRole("button", { name: /allow once/i })
- .click()
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
- await expect(page.locator(promptSelector)).toBeVisible()
- })
+ await clearPermissionDock(page, /allow once/i)
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
+ await expect(page.locator(promptSelector)).toBeVisible()
+ },
+ )
})
})
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
- await withDockSeed(sdk, session.id, async () => {
- await gotoSession(session.id)
-
- await seedSessionPermission(sdk, {
+ await gotoSession(session.id)
+ await setAutoAccept(page, false)
+ await withMockPermission(
+ page,
+ {
+ id: "per_e2e_reject",
sessionID: session.id,
permission: "bash",
- patterns: ["REJECT.md"],
- })
+ patterns: ["/tmp/opencode-e2e-perm-reject"],
+ },
+ undefined,
+ async () => {
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
+ await expect(page.locator(promptSelector)).toHaveCount(0)
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
- await expect(page.locator(promptSelector)).toHaveCount(0)
-
- await page.locator(permissionDockSelector).getByRole("button", { name: /deny/i }).click()
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
- await expect(page.locator(promptSelector)).toBeVisible()
- })
+ await clearPermissionDock(page, /deny/i)
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
+ await expect(page.locator(promptSelector)).toBeVisible()
+ },
+ )
})
})
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
- await withDockSeed(sdk, session.id, async () => {
- await gotoSession(session.id)
-
- await seedSessionPermission(sdk, {
+ await gotoSession(session.id)
+ await setAutoAccept(page, false)
+ await withMockPermission(
+ page,
+ {
+ id: "per_e2e_always",
sessionID: session.id,
permission: "bash",
- patterns: ["README.md"],
- description: "Need permission for command",
+ patterns: ["/tmp/opencode-e2e-perm-always"],
+ metadata: { description: "Need permission for command" },
+ },
+ undefined,
+ async () => {
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
+ await expect(page.locator(promptSelector)).toHaveCount(0)
+
+ await clearPermissionDock(page, /allow always/i)
+ await page.goto(page.url())
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
+ await expect(page.locator(promptSelector)).toBeVisible()
+ },
+ )
+ })
+})
+
+test("child session question request blocks parent dock and unblocks after submit", async ({
+ page,
+ sdk,
+ gotoSession,
+}) => {
+ await withDockSession(sdk, "e2e composer dock child question parent", async (session) => {
+ await gotoSession(session.id)
+
+ const child = await sdk.session
+ .create({
+ title: "e2e composer dock child question",
+ parentID: session.id,
})
+ .then((r) => r.data)
+ if (!child?.id) throw new Error("Child session create did not return an id")
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
- await expect(page.locator(promptSelector)).toHaveCount(0)
+ try {
+ await withDockSeed(sdk, child.id, async () => {
+ await seedSessionQuestion(sdk, {
+ sessionID: child.id,
+ questions: [
+ {
+ header: "Child input",
+ question: "Pick one child option",
+ options: [
+ { label: "Continue", description: "Continue child" },
+ { label: "Stop", description: "Stop child" },
+ ],
+ },
+ ],
+ })
- await page
- .locator(permissionDockSelector)
- .getByRole("button", { name: /allow always/i })
- .click()
- await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
- await expect(page.locator(promptSelector)).toBeVisible()
- })
+ const dock = page.locator(questionDockSelector)
+ await expect.poll(() => dock.count(), { timeout: 10_000 }).toBe(1)
+ await expect(page.locator(promptSelector)).toHaveCount(0)
+
+ await dock.locator('[data-slot="question-option"]').first().click()
+ await dock.getByRole("button", { name: /submit/i }).click()
+
+ await expect.poll(() => page.locator(questionDockSelector).count(), { timeout: 10_000 }).toBe(0)
+ await expect(page.locator(promptSelector)).toBeVisible()
+ })
+ } finally {
+ await cleanupSession({ sdk, sessionID: child.id })
+ }
+ })
+})
+
+test("child session permission request blocks parent dock and supports allow once", async ({
+ page,
+ sdk,
+ gotoSession,
+}) => {
+ await withDockSession(sdk, "e2e composer dock child permission parent", async (session) => {
+ await gotoSession(session.id)
+ await setAutoAccept(page, false)
+
+ const child = await sdk.session
+ .create({
+ title: "e2e composer dock child permission",
+ parentID: session.id,
+ })
+ .then((r) => r.data)
+ if (!child?.id) throw new Error("Child session create did not return an id")
+
+ try {
+ await withMockPermission(
+ page,
+ {
+ id: "per_e2e_child",
+ sessionID: child.id,
+ permission: "bash",
+ patterns: ["/tmp/opencode-e2e-perm-child"],
+ metadata: { description: "Need child permission" },
+ },
+ { child },
+ async () => {
+ await page.goto(page.url())
+ const dock = page.locator(permissionDockSelector)
+ await expect.poll(() => dock.count(), { timeout: 10_000 }).toBe(1)
+ await expect(page.locator(promptSelector)).toHaveCount(0)
+
+ await clearPermissionDock(page, /allow once/i)
+ await page.goto(page.url())
+
+ await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
+ await expect(page.locator(promptSelector)).toBeVisible()
+ },
+ )
+ } finally {
+ await cleanupSession({ sdk, sessionID: child.id })
+ }
})
})
diff --git a/packages/app/e2e/session/session-undo-redo.spec.ts b/packages/app/e2e/session/session-undo-redo.spec.ts
index c6ea2aea0a..eb0840f7cc 100644
--- a/packages/app/e2e/session/session-undo-redo.spec.ts
+++ b/packages/app/e2e/session/session-undo-redo.spec.ts
@@ -45,7 +45,7 @@ async function seedConversation(input: {
.toBe(true)
if (!userMessageID) throw new Error("Expected a user message id")
- await expect(input.page.locator(`[data-message-id="${userMessageID}"]`).first()).toBeVisible({ timeout: 30_000 })
+ await expect(input.page.locator(`[data-message-id="${userMessageID}"]`)).toHaveCount(1, { timeout: 30_000 })
return { prompt, userMessageID }
}
@@ -123,7 +123,7 @@ test("slash redo clears revert and restores latest state", async ({ page, withPr
.toBeUndefined()
await expect(seeded.prompt).not.toContainText(token)
- await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`).first()).toBeVisible()
+ await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`)).toHaveCount(1)
})
})
})
@@ -158,8 +158,8 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro
const firstMessage = page.locator(`[data-message-id="${first.userMessageID}"]`)
const secondMessage = page.locator(`[data-message-id="${second.userMessageID}"]`)
- await expect(firstMessage.first()).toBeVisible()
- await expect(secondMessage.first()).toBeVisible()
+ await expect(firstMessage).toHaveCount(1)
+ await expect(secondMessage).toHaveCount(1)
await second.prompt.click()
await page.keyboard.press(`${modKey}+A`)
@@ -176,7 +176,7 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro
})
.toBe(second.userMessageID)
- await expect(firstMessage.first()).toBeVisible()
+ await expect(firstMessage).toHaveCount(1)
await expect(secondMessage).toHaveCount(0)
await second.prompt.click()
@@ -210,7 +210,7 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro
})
.toBe(second.userMessageID)
- await expect(firstMessage.first()).toBeVisible()
+ await expect(firstMessage).toHaveCount(1)
await expect(secondMessage).toHaveCount(0)
await second.prompt.click()
@@ -226,8 +226,8 @@ test("slash undo/redo traverses multi-step revert stack", async ({ page, withPro
})
.toBeUndefined()
- await expect(firstMessage.first()).toBeVisible()
- await expect(secondMessage.first()).toBeVisible()
+ await expect(firstMessage).toHaveCount(1)
+ await expect(secondMessage).toHaveCount(1)
})
})
})
diff --git a/packages/app/e2e/settings/settings-keybinds.spec.ts b/packages/app/e2e/settings/settings-keybinds.spec.ts
index 5e98bd158a..e0d590b31a 100644
--- a/packages/app/e2e/settings/settings-keybinds.spec.ts
+++ b/packages/app/e2e/settings/settings-keybinds.spec.ts
@@ -32,22 +32,19 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
await closeDialog(page, dialog)
- const main = page.locator("main")
- const initialClasses = (await main.getAttribute("class")) ?? ""
- const initiallyClosed = initialClasses.includes("xl:border-l")
+ const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
+ const initiallyClosed = (await button.getAttribute("aria-expanded")) !== "true"
await page.keyboard.press(`${modKey}+Shift+H`)
- await page.waitForTimeout(100)
+ await expect(button).toHaveAttribute("aria-expanded", initiallyClosed ? "true" : "false")
- const afterToggleClasses = (await main.getAttribute("class")) ?? ""
- const afterToggleClosed = afterToggleClasses.includes("xl:border-l")
+ const afterToggleClosed = (await button.getAttribute("aria-expanded")) !== "true"
expect(afterToggleClosed).toBe(!initiallyClosed)
await page.keyboard.press(`${modKey}+Shift+H`)
- await page.waitForTimeout(100)
+ await expect(button).toHaveAttribute("aria-expanded", initiallyClosed ? "false" : "true")
- const finalClasses = (await main.getAttribute("class")) ?? ""
- const finalClosed = finalClasses.includes("xl:border-l")
+ const finalClosed = (await button.getAttribute("aria-expanded")) !== "true"
expect(finalClosed).toBe(initiallyClosed)
})
diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts
index 9fbcf79f5e..f25e91a315 100644
--- a/packages/app/e2e/settings/settings.spec.ts
+++ b/packages/app/e2e/settings/settings.spec.ts
@@ -9,7 +9,6 @@ import {
settingsNotificationsPermissionsSelector,
settingsReleaseNotesSelector,
settingsSoundsAgentSelector,
- settingsSoundsAgentEnabledSelector,
settingsSoundsErrorsSelector,
settingsSoundsPermissionsSelector,
settingsThemeSelector,
@@ -84,16 +83,23 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) =>
const select = dialog.locator(settingsThemeSelector)
await expect(select).toBeVisible()
+ const currentThemeId = await page.evaluate(() => {
+ return document.documentElement.getAttribute("data-theme")
+ })
+ const currentTheme = (await select.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
+
await select.locator('[data-slot="select-select-trigger"]').click()
const items = page.locator('[data-slot="select-select-item"]')
const count = await items.count()
expect(count).toBeGreaterThan(1)
- const firstTheme = await items.nth(1).locator('[data-slot="select-select-item-label"]').textContent()
- expect(firstTheme).toBeTruthy()
+ const nextTheme = (await items.locator('[data-slot="select-select-item-label"]').allTextContents())
+ .map((x) => x.trim())
+ .find((x) => x && x !== currentTheme)
+ expect(nextTheme).toBeTruthy()
- await items.nth(1).click()
+ await items.filter({ hasText: nextTheme! }).first().click()
await page.keyboard.press("Escape")
@@ -102,7 +108,7 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) =>
})
expect(storedThemeId).not.toBeNull()
- expect(storedThemeId).not.toBe("oc-1")
+ expect(storedThemeId).not.toBe(currentThemeId)
const dataTheme = await page.evaluate(() => {
return document.documentElement.getAttribute("data-theme")
@@ -110,6 +116,42 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) =>
expect(dataTheme).toBe(storedThemeId)
})
+test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("opencode-theme-id", "oc-1")
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
+ localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
+ })
+
+ await gotoSession()
+
+ await expect(page.locator("html")).toHaveAttribute("data-theme", "oc-2")
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-id")
+ })
+ })
+ .toBe("oc-2")
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-css-light")
+ })
+ })
+ .toBeNull()
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-css-dark")
+ })
+ })
+ .toBeNull()
+})
+
test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => {
await gotoSession()
@@ -336,21 +378,19 @@ test("changing sound agent selection persists in localStorage", async ({ page, g
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
})
-test("disabling agent sound disables sound selection", async ({ page, gotoSession }) => {
+test("selecting none disables agent sound", async ({ page, gotoSession }) => {
await gotoSession()
const dialog = await openSettings(page)
const select = dialog.locator(settingsSoundsAgentSelector)
- const switchContainer = dialog.locator(settingsSoundsAgentEnabledSelector)
const trigger = select.locator('[data-slot="select-select-trigger"]')
await expect(select).toBeVisible()
- await expect(switchContainer).toBeVisible()
await expect(trigger).toBeEnabled()
- await switchContainer.locator('[data-slot="switch-control"]').click()
- await page.waitForTimeout(100)
-
- await expect(trigger).toBeDisabled()
+ await trigger.click()
+ const items = page.locator('[data-slot="select-select-item"]')
+ await expect(items.first()).toBeVisible()
+ await items.first().click()
const stored = await page.evaluate((key) => {
const raw = localStorage.getItem(key)
diff --git a/packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts b/packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts
index e37f94f3a7..d10fca0e49 100644
--- a/packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts
+++ b/packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts
@@ -1,6 +1,6 @@
import { test, expect } from "../fixtures"
-import { closeSidebar, hoverSessionItem } from "../actions"
-import { projectSwitchSelector, sessionItemSelector } from "../selectors"
+import { cleanupSession, closeSidebar, hoverSessionItem } from "../actions"
+import { projectSwitchSelector } from "../selectors"
test("collapsed sidebar popover stays open when archiving a session", async ({ page, slug, sdk, gotoSession }) => {
const stamp = Date.now()
@@ -15,12 +15,15 @@ test("collapsed sidebar popover stays open when archiving a session", async ({ p
await gotoSession(one.id)
await closeSidebar(page)
+ const oneItem = page.locator(`[data-session-id="${one.id}"]`).last()
+ const twoItem = page.locator(`[data-session-id="${two.id}"]`).last()
+
const project = page.locator(projectSwitchSelector(slug)).first()
await expect(project).toBeVisible()
await project.hover()
- await expect(page.locator(sessionItemSelector(one.id)).first()).toBeVisible()
- await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
+ await expect(oneItem).toBeVisible()
+ await expect(twoItem).toBeVisible()
const item = await hoverSessionItem(page, one.id)
await item
@@ -28,9 +31,9 @@ test("collapsed sidebar popover stays open when archiving a session", async ({ p
.first()
.click()
- await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
+ await expect(twoItem).toBeVisible()
} finally {
- await sdk.session.delete({ sessionID: one.id }).catch(() => undefined)
- await sdk.session.delete({ sessionID: two.id }).catch(() => undefined)
+ await cleanupSession({ sdk, sessionID: one.id })
+ await cleanupSession({ sdk, sessionID: two.id })
}
})
diff --git a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts
index cda2278a95..22f98e94ca 100644
--- a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts
+++ b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts
@@ -1,5 +1,5 @@
import { test, expect } from "../fixtures"
-import { openSidebar, withSession } from "../actions"
+import { cleanupSession, openSidebar, withSession } from "../actions"
import { promptSelector } from "../selectors"
test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => {
@@ -18,14 +18,13 @@ test("sidebar session links navigate to the selected session", async ({ page, sl
const target = page.locator(`[data-session-id="${two.id}"] a`).first()
await expect(target).toBeVisible()
- await target.scrollIntoViewIfNeeded()
await target.click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
await expect(page.locator(promptSelector)).toBeVisible()
await expect(page.locator(`[data-session-id="${two.id}"] a`).first()).toHaveClass(/\bactive\b/)
} finally {
- await sdk.session.delete({ sessionID: one.id }).catch(() => undefined)
- await sdk.session.delete({ sessionID: two.id }).catch(() => undefined)
+ await cleanupSession({ sdk, sessionID: one.id })
+ await cleanupSession({ sdk, sessionID: two.id })
}
})
diff --git a/packages/app/e2e/sidebar/sidebar.spec.ts b/packages/app/e2e/sidebar/sidebar.spec.ts
index 5c78c2220d..c6bf3fa9ab 100644
--- a/packages/app/e2e/sidebar/sidebar.spec.ts
+++ b/packages/app/e2e/sidebar/sidebar.spec.ts
@@ -5,12 +5,14 @@ test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
await gotoSession()
await openSidebar(page)
+ const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
+ await expect(button).toHaveAttribute("aria-expanded", "true")
await toggleSidebar(page)
- await expect(page.locator("main")).toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "false")
await toggleSidebar(page)
- await expect(page.locator("main")).not.toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "true")
})
test("sidebar collapsed state persists across navigation and reload", async ({ page, sdk, gotoSession }) => {
@@ -19,14 +21,15 @@ test("sidebar collapsed state persists across navigation and reload", async ({ p
await gotoSession(session1.id)
await openSidebar(page)
+ const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
await toggleSidebar(page)
- await expect(page.locator("main")).toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "false")
await gotoSession(session2.id)
- await expect(page.locator("main")).toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "false")
await page.reload()
- await expect(page.locator("main")).toHaveClass(/xl:border-l/)
+ await expect(button).toHaveAttribute("aria-expanded", "false")
const opened = await page.evaluate(
() => JSON.parse(localStorage.getItem("opencode.global.dat:layout") ?? "{}").sidebar?.opened,
diff --git a/packages/app/e2e/terminal/terminal-tabs.spec.ts b/packages/app/e2e/terminal/terminal-tabs.spec.ts
new file mode 100644
index 0000000000..afa6254cd0
--- /dev/null
+++ b/packages/app/e2e/terminal/terminal-tabs.spec.ts
@@ -0,0 +1,139 @@
+import type { Page } from "@playwright/test"
+import { test, expect } from "../fixtures"
+import { terminalSelector } from "../selectors"
+import { terminalToggleKey, workspacePersistKey } from "../utils"
+
+type State = {
+ active?: string
+ all: Array<{
+ id: string
+ title: string
+ titleNumber: number
+ buffer?: string
+ }>
+}
+
+async function open(page: Page) {
+ const terminal = page.locator(terminalSelector)
+ const visible = await terminal.isVisible().catch(() => false)
+ if (!visible) await page.keyboard.press(terminalToggleKey)
+ await expect(terminal).toBeVisible()
+ await expect(terminal.locator("textarea")).toHaveCount(1)
+}
+
+async function run(page: Page, cmd: string) {
+ const terminal = page.locator(terminalSelector)
+ await expect(terminal).toBeVisible()
+ await terminal.click()
+ await page.keyboard.type(cmd)
+ await page.keyboard.press("Enter")
+}
+
+async function store(page: Page, key: string) {
+ return page.evaluate((key) => {
+ const raw = localStorage.getItem(key)
+ if (raw) return JSON.parse(raw) as State
+
+ for (let i = 0; i < localStorage.length; i++) {
+ const next = localStorage.key(i)
+ if (!next?.endsWith(":workspace:terminal")) continue
+ const value = localStorage.getItem(next)
+ if (!value) continue
+ return JSON.parse(value) as State
+ }
+ }, key)
+}
+
+test("inactive terminal tab buffers persist across tab switches", async ({ page, withProject }) => {
+ await withProject(async ({ directory, gotoSession }) => {
+ const key = workspacePersistKey(directory, "terminal")
+ const one = `E2E_TERM_ONE_${Date.now()}`
+ const two = `E2E_TERM_TWO_${Date.now()}`
+ const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
+ const first = tabs.filter({ hasText: /Terminal 1/ }).first()
+ const second = tabs.filter({ hasText: /Terminal 2/ }).first()
+
+ await gotoSession()
+ await open(page)
+
+ await run(page, `echo ${one}`)
+
+ await page.getByRole("button", { name: /new terminal/i }).click()
+ await expect(tabs).toHaveCount(2)
+
+ await run(page, `echo ${two}`)
+
+ await first.click()
+ await expect(first).toHaveAttribute("aria-selected", "true")
+ await expect
+ .poll(
+ async () => {
+ const state = await store(page, key)
+ const first = state?.all.find((item) => item.titleNumber === 1)?.buffer ?? ""
+ const second = state?.all.find((item) => item.titleNumber === 2)?.buffer ?? ""
+ return {
+ first: first.includes(one),
+ second: second.includes(two),
+ }
+ },
+ { timeout: 30_000 },
+ )
+ .toEqual({ first: false, second: true })
+
+ await second.click()
+ await expect(second).toHaveAttribute("aria-selected", "true")
+ await expect
+ .poll(
+ async () => {
+ const state = await store(page, key)
+ const first = state?.all.find((item) => item.titleNumber === 1)?.buffer ?? ""
+ const second = state?.all.find((item) => item.titleNumber === 2)?.buffer ?? ""
+ return {
+ first: first.includes(one),
+ second: second.includes(two),
+ }
+ },
+ { timeout: 30_000 },
+ )
+ .toEqual({ first: true, second: false })
+ })
+})
+
+test("closing the active terminal tab falls back to the previous tab", async ({ page, withProject }) => {
+ await withProject(async ({ directory, gotoSession }) => {
+ const key = workspacePersistKey(directory, "terminal")
+ const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
+
+ await gotoSession()
+ await open(page)
+
+ await page.getByRole("button", { name: /new terminal/i }).click()
+ await expect(tabs).toHaveCount(2)
+
+ const second = tabs.filter({ hasText: /Terminal 2/ }).first()
+ await second.click()
+ await expect(second).toHaveAttribute("aria-selected", "true")
+
+ await second.hover()
+ await page
+ .getByRole("button", { name: /close terminal/i })
+ .nth(1)
+ .click({ force: true })
+
+ const first = tabs.filter({ hasText: /Terminal 1/ }).first()
+ await expect(tabs).toHaveCount(1)
+ await expect(first).toHaveAttribute("aria-selected", "true")
+ await expect
+ .poll(
+ async () => {
+ const state = await store(page, key)
+ return {
+ count: state?.all.length ?? 0,
+ first: state?.all.some((item) => item.titleNumber === 1) ?? false,
+ }
+ },
+ { timeout: 15_000 },
+ )
+ .toEqual({ count: 1, first: true })
+ })
+})
diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts
index ec6cdf8302..f07a8d3f11 100644
--- a/packages/app/e2e/utils.ts
+++ b/packages/app/e2e/utils.ts
@@ -1,12 +1,28 @@
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Encode, checksum } from "@opencode-ai/util/encode"
-export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
+export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
export const serverUrl = `http://${serverHost}:${serverPort}`
export const serverName = `${serverHost}:${serverPort}`
+const localHosts = ["127.0.0.1", "localhost"]
+
+const serverLabels = (() => {
+ const url = new URL(serverUrl)
+ if (!localHosts.includes(url.hostname)) return [serverName]
+ return localHosts.map((host) => `${host}:${url.port}`)
+})()
+
+export const serverNames = [...new Set(serverLabels)]
+
+export const serverUrls = serverNames.map((name) => `http://${name}`)
+
+const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+
+export const serverNamePattern = new RegExp(`(?:${serverNames.map(escape).join("|")})`)
+
export const modKey = process.platform === "darwin" ? "Meta" : "Control"
export const terminalToggleKey = "Control+Backquote"
@@ -14,6 +30,12 @@ export function createSdk(directory?: string) {
return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true })
}
+export async function resolveDirectory(directory: string) {
+ return createSdk(directory)
+ .path.get()
+ .then((x) => x.data?.directory ?? directory)
+}
+
export async function getWorktree() {
const sdk = createSdk()
const result = await sdk.path.get()
@@ -33,3 +55,9 @@ export function dirPath(directory: string) {
export function sessionPath(directory: string, sessionID?: string) {
return `${dirPath(directory)}/session${sessionID ? `/${sessionID}` : ""}`
}
+
+export function workspacePersistKey(directory: string, key: string) {
+ const head = (directory.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
+ const sum = checksum(directory) ?? "0"
+ return `opencode.workspace.${head}.${sum}.dat:workspace:${key}`
+}
diff --git a/packages/app/package.json b/packages/app/package.json
index b9397b0f40..10ef17d1bf 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.2.10",
+ "version": "1.2.24",
"description": "",
"type": "module",
"exports": {
@@ -57,7 +57,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
- "ghostty-web": "0.4.0",
+ "ghostty-web": "github:anomalyco/ghostty-web#main",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
diff --git a/packages/app/playwright.config.ts b/packages/app/playwright.config.ts
index ea85829e0b..a97c826514 100644
--- a/packages/app/playwright.config.ts
+++ b/packages/app/playwright.config.ts
@@ -1,8 +1,8 @@
import { defineConfig, devices } from "@playwright/test"
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
-const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${port}`
-const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
+const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`
+const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
const reuse = !process.env.CI
diff --git a/packages/app/public/oc-theme-preload.js b/packages/app/public/oc-theme-preload.js
index f8c7104961..36fa5d726a 100644
--- a/packages/app/public/oc-theme-preload.js
+++ b/packages/app/public/oc-theme-preload.js
@@ -1,6 +1,13 @@
;(function () {
- var themeId = localStorage.getItem("opencode-theme-id")
- if (!themeId) return
+ var key = "opencode-theme-id"
+ var themeId = localStorage.getItem(key) || "oc-2"
+
+ if (themeId === "oc-1") {
+ themeId = "oc-2"
+ localStorage.setItem(key, themeId)
+ localStorage.removeItem("opencode-theme-css-light")
+ localStorage.removeItem("opencode-theme-css-dark")
+ }
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
@@ -9,9 +16,9 @@
document.documentElement.dataset.theme = themeId
document.documentElement.dataset.colorScheme = mode
- if (themeId === "oc-1") return
+ if (themeId === "oc-2") return
- var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
+ var css = localStorage.getItem("opencode-theme-css-" + mode)
if (css) {
var style = document.createElement("style")
style.id = "oc-theme-preload"
diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts
index 112e2bc60a..9a83411b1d 100644
--- a/packages/app/script/e2e-local.ts
+++ b/packages/app/script/e2e-local.ts
@@ -145,6 +145,7 @@ try {
Object.assign(process.env, serverEnv)
process.env.AGENT = "1"
process.env.OPENCODE = "1"
+ process.env.OPENCODE_PID = String(process.pid)
const log = await import("../../opencode/src/util/log")
const install = await import("../../opencode/src/installation")
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index 1be9f38d74..52a1dac6a2 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -1,16 +1,14 @@
import "@/index.css"
-import { Code } from "@opencode-ai/ui/code"
+import { File } from "@opencode-ai/ui/file"
import { I18nProvider } from "@opencode-ai/ui/context"
-import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
-import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
+import { FileComponentProvider } from "@opencode-ai/ui/context/file"
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
-import { Diff } from "@opencode-ai/ui/diff"
import { Font } from "@opencode-ai/ui/font"
import { ThemeProvider } from "@opencode-ai/ui/theme"
import { MetaProvider } from "@solidjs/meta"
-import { Navigate, Route, Router } from "@solidjs/router"
-import { ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
+import { BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
+import { Component, ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
import { CommandProvider } from "@/context/command"
import { CommentsProvider } from "@/context/comments"
import { FileProvider } from "@/context/file"
@@ -30,6 +28,7 @@ import { TerminalProvider } from "@/context/terminal"
import DirectoryLayout from "@/pages/directory-layout"
import Layout from "@/pages/layout"
import { ErrorPage } from "./pages/error"
+import { Dynamic } from "solid-js/web"
const Home = lazy(() => import("@/pages/home"))
const Session = lazy(() => import("@/pages/session"))
@@ -122,9 +121,7 @@ export function AppBaseProviders(props: ParentProps) {
}>
-
- {props.children}
-
+ {props.children}
@@ -148,13 +145,15 @@ export function AppInterface(props: {
children?: JSX.Element
defaultServer: ServerConnection.Key
servers?: Array
+ router?: Component
}) {
return (
- {routerProps.children} }
>
@@ -162,7 +161,7 @@ export function AppInterface(props: {
-
+
diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx
new file mode 100644
index 0000000000..93a4654f86
--- /dev/null
+++ b/packages/app/src/components/debug-bar.tsx
@@ -0,0 +1,432 @@
+import { useIsRouting, useLocation } from "@solidjs/router"
+import { batch, createEffect, onCleanup, onMount } from "solid-js"
+import { createStore } from "solid-js/store"
+import { Tooltip } from "@opencode-ai/ui/tooltip"
+
+type Mem = Performance & {
+ memory?: {
+ usedJSHeapSize: number
+ jsHeapSizeLimit: number
+ }
+}
+
+type Evt = PerformanceEntry & {
+ interactionId?: number
+ processingStart?: number
+}
+
+type Shift = PerformanceEntry & {
+ hadRecentInput: boolean
+ value: number
+}
+
+type Obs = PerformanceObserverInit & {
+ durationThreshold?: number
+}
+
+const span = 5000
+
+const ms = (n?: number, d = 0) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ return `${n.toFixed(d)}ms`
+}
+
+const time = (n?: number) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ return `${Math.round(n)}`
+}
+
+const mb = (n?: number) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ const v = n / 1024 / 1024
+ return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB`
+}
+
+const bad = (n: number | undefined, limit: number, low = false) => {
+ if (n === undefined || Number.isNaN(n)) return false
+ return low ? n < limit : n > limit
+}
+
+const session = (path: string) => path.includes("/session")
+
+function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string }) {
+ return (
+
+
+
{props.label}
+
+ {props.value}
+
+
+
+ )
+}
+
+export function DebugBar() {
+ const location = useLocation()
+ const routing = useIsRouting()
+ const [state, setState] = createStore({
+ cls: undefined as number | undefined,
+ delay: undefined as number | undefined,
+ fps: undefined as number | undefined,
+ gap: undefined as number | undefined,
+ heap: {
+ limit: undefined as number | undefined,
+ used: undefined as number | undefined,
+ },
+ inp: undefined as number | undefined,
+ jank: undefined as number | undefined,
+ long: {
+ block: undefined as number | undefined,
+ count: undefined as number | undefined,
+ max: undefined as number | undefined,
+ },
+ nav: {
+ dur: undefined as number | undefined,
+ pending: false,
+ },
+ })
+
+ const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined)
+ const heapv = () => {
+ const value = heap()
+ if (value === undefined) return "n/a"
+ return `${Math.round(value * 100)}%`
+ }
+ const longv = () => (state.long.count === undefined ? "n/a" : `${time(state.long.block)}/${state.long.count}`)
+ const navv = () => (state.nav.pending ? "..." : time(state.nav.dur))
+
+ let prev = ""
+ let start = 0
+ let init = false
+ let one = 0
+ let two = 0
+
+ createEffect(() => {
+ const busy = routing()
+ const next = `${location.pathname}${location.search}`
+
+ if (!init) {
+ init = true
+ prev = next
+ return
+ }
+
+ if (busy) {
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ one = 0
+ two = 0
+ if (start !== 0) return
+ start = performance.now()
+ if (session(prev)) setState("nav", { dur: undefined, pending: true })
+ return
+ }
+
+ if (start === 0) {
+ prev = next
+ return
+ }
+
+ const at = start
+ const from = prev
+ start = 0
+ prev = next
+
+ if (!(session(from) || session(next))) return
+
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ one = requestAnimationFrame(() => {
+ one = 0
+ two = requestAnimationFrame(() => {
+ two = 0
+ setState("nav", { dur: performance.now() - at, pending: false })
+ })
+ })
+ })
+
+ onMount(() => {
+ const obs: PerformanceObserver[] = []
+ const fps: Array<{ at: number; dur: number }> = []
+ const long: Array<{ at: number; dur: number }> = []
+ const seen = new Map()
+ let hasLong = false
+ let poll: number | undefined
+ let raf = 0
+ let last = 0
+ let snap = 0
+
+ const trim = (list: Array<{ at: number; dur: number }>, span: number, at: number) => {
+ while (list[0] && at - list[0].at > span) list.shift()
+ }
+
+ const syncFrame = (at: number) => {
+ trim(fps, span, at)
+ const total = fps.reduce((sum, entry) => sum + entry.dur, 0)
+ const gap = fps.reduce((max, entry) => Math.max(max, entry.dur), 0)
+ const jank = fps.filter((entry) => entry.dur > 32).length
+ batch(() => {
+ setState("fps", total > 0 ? (fps.length * 1000) / total : undefined)
+ setState("gap", gap > 0 ? gap : undefined)
+ setState("jank", jank)
+ })
+ }
+
+ const syncLong = (at = performance.now()) => {
+ if (!hasLong) return
+ trim(long, span, at)
+ const block = long.reduce((sum, entry) => sum + Math.max(0, entry.dur - 50), 0)
+ const max = long.reduce((hi, entry) => Math.max(hi, entry.dur), 0)
+ setState("long", { block, count: long.length, max })
+ }
+
+ const syncInp = (at = performance.now()) => {
+ for (const [key, entry] of seen) {
+ if (at - entry.at > span) seen.delete(key)
+ }
+ let delay = 0
+ let inp = 0
+ for (const entry of seen.values()) {
+ delay = Math.max(delay, entry.delay)
+ inp = Math.max(inp, entry.dur)
+ }
+ batch(() => {
+ setState("delay", delay > 0 ? delay : undefined)
+ setState("inp", inp > 0 ? inp : undefined)
+ })
+ }
+
+ const syncHeap = () => {
+ const mem = (performance as Mem).memory
+ if (!mem) return
+ setState("heap", { limit: mem.jsHeapSizeLimit, used: mem.usedJSHeapSize })
+ }
+
+ const reset = () => {
+ fps.length = 0
+ long.length = 0
+ seen.clear()
+ last = 0
+ snap = 0
+ batch(() => {
+ setState("fps", undefined)
+ setState("gap", undefined)
+ setState("jank", undefined)
+ setState("delay", undefined)
+ setState("inp", undefined)
+ if (hasLong) setState("long", { block: 0, count: 0, max: 0 })
+ })
+ }
+
+ const watch = (type: string, init: Obs, fn: (entries: PerformanceEntry[]) => void) => {
+ if (typeof PerformanceObserver === "undefined") return false
+ if (!(PerformanceObserver.supportedEntryTypes ?? []).includes(type)) return false
+ const ob = new PerformanceObserver((list) => fn(list.getEntries()))
+ try {
+ ob.observe(init)
+ obs.push(ob)
+ return true
+ } catch {
+ ob.disconnect()
+ return false
+ }
+ }
+
+ if (
+ watch("layout-shift", { buffered: true, type: "layout-shift" }, (entries) => {
+ const add = entries.reduce((sum, entry) => {
+ const item = entry as Shift
+ if (item.hadRecentInput) return sum
+ return sum + item.value
+ }, 0)
+ if (add === 0) return
+ setState("cls", (value) => (value ?? 0) + add)
+ })
+ ) {
+ setState("cls", 0)
+ }
+
+ if (
+ watch("longtask", { buffered: true, type: "longtask" }, (entries) => {
+ const at = performance.now()
+ long.push(...entries.map((entry) => ({ at: entry.startTime, dur: entry.duration })))
+ syncLong(at)
+ })
+ ) {
+ hasLong = true
+ setState("long", { block: 0, count: 0, max: 0 })
+ }
+
+ watch("event", { buffered: true, durationThreshold: 16, type: "event" }, (entries) => {
+ for (const raw of entries) {
+ const entry = raw as Evt
+ if (entry.duration < 16) continue
+ const key =
+ entry.interactionId && entry.interactionId > 0
+ ? entry.interactionId
+ : `${entry.name}:${Math.round(entry.startTime)}`
+ const prev = seen.get(key)
+ const delay = Math.max(0, (entry.processingStart ?? entry.startTime) - entry.startTime)
+ seen.set(key, {
+ at: entry.startTime,
+ delay: Math.max(prev?.delay ?? 0, delay),
+ dur: Math.max(prev?.dur ?? 0, entry.duration),
+ })
+ if (seen.size <= 200) continue
+ const first = seen.keys().next().value
+ if (first !== undefined) seen.delete(first)
+ }
+ syncInp()
+ })
+
+ const loop = (at: number) => {
+ if (document.visibilityState !== "visible") {
+ raf = 0
+ return
+ }
+
+ if (last === 0) {
+ last = at
+ raf = requestAnimationFrame(loop)
+ return
+ }
+
+ fps.push({ at, dur: at - last })
+ last = at
+
+ if (at - snap >= 250) {
+ snap = at
+ syncFrame(at)
+ }
+
+ raf = requestAnimationFrame(loop)
+ }
+
+ const stop = () => {
+ if (raf !== 0) cancelAnimationFrame(raf)
+ raf = 0
+ if (poll === undefined) return
+ clearInterval(poll)
+ poll = undefined
+ }
+
+ const start = () => {
+ if (document.visibilityState !== "visible") return
+ if (poll === undefined) {
+ poll = window.setInterval(() => {
+ syncLong()
+ syncInp()
+ syncHeap()
+ }, 1000)
+ }
+ if (raf !== 0) return
+ raf = requestAnimationFrame(loop)
+ }
+
+ const vis = () => {
+ if (document.visibilityState !== "visible") {
+ stop()
+ return
+ }
+ reset()
+ start()
+ }
+
+ syncHeap()
+ start()
+ document.addEventListener("visibilitychange", vis)
+
+ onCleanup(() => {
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ stop()
+ document.removeEventListener("visibilitychange", vis)
+ for (const ob of obs) ob.disconnect()
+ })
+ })
+
+ return (
+
+ )
+}
diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx
index 90f4f41f7c..b042205cf4 100644
--- a/packages/app/src/components/dialog-connect-provider.tsx
+++ b/packages/app/src/components/dialog-connect-provider.tsx
@@ -4,7 +4,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
-import type { IconName } from "@opencode-ai/ui/icons/provider"
import { List, type ListRef } from "@opencode-ai/ui/list"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Spinner } from "@opencode-ai/ui/spinner"
@@ -447,7 +446,7 @@ export function DialogConnectProvider(props: { provider: string }) {
>
-
+
diff --git a/packages/app/src/components/dialog-release-notes.tsx b/packages/app/src/components/dialog-release-notes.tsx
index 2040009a8c..d0a35b71be 100644
--- a/packages/app/src/components/dialog-release-notes.tsx
+++ b/packages/app/src/components/dialog-release-notes.tsx
@@ -2,6 +2,7 @@ import { createSignal } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
export type Highlight = {
@@ -16,6 +17,7 @@ export type Highlight = {
export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
const dialog = useDialog()
+ const language = useLanguage()
const settings = useSettings()
const [index, setIndex] = createSignal(0)
@@ -83,16 +85,16 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
{isLast() ? (
- Get started
+ {language.t("dialog.releaseNotes.action.getStarted")}
) : (
- Next
+ {language.t("dialog.releaseNotes.action.next")}
)}
- Don't show these in the future
+ {language.t("dialog.releaseNotes.action.hideFuture")}
@@ -128,7 +130,7 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
{feature()!.media!.type === "image" ? (
) : (
diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx
index 515e640c9f..91e23f8ffa 100644
--- a/packages/app/src/components/dialog-select-directory.tsx
+++ b/packages/app/src/components/dialog-select-directory.tsx
@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
import { createMemo, createResource, createSignal } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
+import { useLayout } from "@/context/layout"
import { useLanguage } from "@/context/language"
interface DialogSelectDirectoryProps {
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
type Row = {
absolute: string
search: string
+ group: "recent" | "folders"
}
function cleanInput(value: string) {
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
return tildeOf(full, home) || full
}
-function toRow(absolute: string, home: string): Row {
+function toRow(absolute: string, home: string, group: Row["group"]): Row {
const full = trimTrailing(absolute)
const tilde = tildeOf(full, home)
const withSlash = (value: string) => {
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
const search = Array.from(
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
).join("\n")
- return { absolute: full, search }
+ return { absolute: full, search, group }
+}
+
+function uniqueRows(rows: Row[]) {
+ const seen = new Set()
+ return rows.filter((row) => {
+ if (seen.has(row.absolute)) return false
+ seen.add(row.absolute)
+ return true
+ })
}
function useDirectorySearch(args: {
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
const sync = useGlobalSync()
const sdk = useGlobalSDK()
+ const layout = useLayout()
const dialog = useDialog()
const language = useLanguage()
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
start,
})
+ const recentProjects = createMemo(() => {
+ const projects = layout.projects.list()
+ const byProject = new Map()
+
+ for (const project of projects) {
+ let at = 0
+ const dirs = [project.worktree, ...(project.sandboxes ?? [])]
+ for (const directory of dirs) {
+ const sessions = sync.child(directory, { bootstrap: false })[0].session
+ for (const session of sessions) {
+ if (session.time.archived) continue
+ const updated = session.time.updated ?? session.time.created
+ if (updated > at) at = updated
+ }
+ }
+ byProject.set(project.worktree, at)
+ }
+
+ return projects
+ .map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
+ .sort((a, b) => b.at - a.at || a.index - b.index)
+ .slice(0, 5)
+ .map(({ project }) => {
+ const row = toRow(project.worktree, home(), "recent")
+ const name = project.name || getFilename(project.worktree)
+ return {
+ ...row,
+ search: `${row.search}\n${name}`,
+ }
+ })
+ })
+
const items = async (value: string) => {
const results = await directories(value)
- return results.map((absolute) => toRow(absolute, home()))
+ const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
+ return uniqueRows([...recentProjects(), ...directoryRows])
}
function resolve(absolute: string) {
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
items={items}
key={(x) => x.absolute}
filterKeys={["search"]}
+ groupBy={(item) => item.group}
+ sortGroupsBy={(a, b) => {
+ if (a.category === b.category) return 0
+ return a.category === "recent" ? -1 : 1
+ }}
+ groupHeader={(group) =>
+ group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open")
+ }
ref={(r) => (list = r)}
onFilter={(value) => setFilter(cleanInput(value))}
onKeyEvent={(e, item) => {
diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx
index 29a3666c03..b530aff532 100644
--- a/packages/app/src/components/dialog-select-file.tsx
+++ b/packages/app/src/components/dialog-select-file.tsx
@@ -449,7 +449,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
- {getRelativeTime(new Date(item.updated!).toISOString())}
+ {getRelativeTime(new Date(item.updated!).toISOString(), language.t)}
diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx
index af788d05b0..bcee3f501f 100644
--- a/packages/app/src/components/dialog-select-model-unpaid.tsx
+++ b/packages/app/src/components/dialog-select-model-unpaid.tsx
@@ -1,7 +1,6 @@
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
-import type { IconName } from "@opencode-ai/ui/icons/provider"
import { List, type ListRef } from "@opencode-ai/ui/list"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Tag } from "@opencode-ai/ui/tag"
@@ -95,11 +94,22 @@ export const DialogSelectModelUnpaid: Component = () => {
>
{(i) => (
-
+
{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..e53738399a 100644
--- a/packages/app/src/components/dialog-select-provider.tsx
+++ b/packages/app/src/components/dialog-select-provider.tsx
@@ -5,18 +5,12 @@ import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Tag } from "@opencode-ai/ui/tag"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
-import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { useLanguage } from "@/context/language"
import { DialogCustomProvider } from "./dialog-custom-provider"
const CUSTOM_ID = "_custom"
-function icon(id: string): IconName {
- if (iconNames.includes(id as IconName)) return id as IconName
- return "synthetic"
-}
-
export const DialogSelectProvider: Component = () => {
const dialog = useDialog()
const providers = useProviders()
@@ -29,6 +23,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 (
@@ -68,8 +63,11 @@ export const DialogSelectProvider: Component = () => {
>
{(i) => (
-
+
{i.name}
+
+ {language.t("dialog.provider.opencode.tagline")}
+
{language.t("settings.providers.tag.custom")}
@@ -77,6 +75,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/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx
index 76c8ff60ef..4813ecc45d 100644
--- a/packages/app/src/components/dialog-select-server.tsx
+++ b/packages/app/src/components/dialog-select-server.tsx
@@ -2,6 +2,7 @@ import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
+import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { List } from "@opencode-ai/ui/list"
import { TextField } from "@opencode-ai/ui/text-field"
@@ -9,32 +10,27 @@ import { showToast } from "@opencode-ai/ui/toast"
import { useNavigate } from "@solidjs/router"
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
-import { ServerRow } from "@/components/server/server-row"
+import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
import { checkServerHealth, type ServerHealth } from "@/utils/server-health"
-interface AddRowProps {
- value: string
- placeholder: string
- adding: boolean
- error: string
- status: boolean | undefined
- onChange: (value: string) => void
- onKeyDown: (event: KeyboardEvent) => void
- onBlur: () => void
-}
-
-interface EditRowProps {
+interface ServerFormProps {
value: string
+ name: string
+ username: string
+ password: string
placeholder: string
busy: boolean
error: string
status: boolean | undefined
onChange: (value: string) => void
- onKeyDown: (event: KeyboardEvent) => void
- onBlur: () => void
+ onNameChange: (value: string) => void
+ onUsernameChange: (value: string) => void
+ onPasswordChange: (value: string) => void
+ onSubmit: () => void
+ onBack: () => void
}
function showRequestError(language: ReturnType
, err: unknown) {
@@ -83,83 +79,86 @@ function useServerPreview(fetcher: typeof fetch) {
return host.includes(".") || host.includes(":")
}
- const previewStatus = async (value: string, setStatus: (value: boolean | undefined) => void) => {
+ const previewStatus = async (
+ value: string,
+ username: string,
+ password: string,
+ setStatus: (value: boolean | undefined) => void,
+ ) => {
setStatus(undefined)
if (!looksComplete(value)) return
const normalized = normalizeServerUrl(value)
if (!normalized) return
- const result = await checkServerHealth({ url: normalized }, fetcher)
+ const http: ServerConnection.HttpBase = { url: normalized }
+ if (username) http.username = username
+ if (password) http.password = password
+ const result = await checkServerHealth(http, fetcher)
setStatus(result.healthy)
}
return { previewStatus }
}
-function AddRow(props: AddRowProps) {
- return (
-
-
- )
-}
+function ServerForm(props: ServerFormProps) {
+ const language = useLanguage()
+ const keyDown = (event: KeyboardEvent) => {
+ event.stopPropagation()
+ if (event.key === "Escape") {
+ event.preventDefault()
+ props.onBack()
+ return
+ }
+ if (event.key !== "Enter" || event.isComposing) return
+ event.preventDefault()
+ props.onSubmit()
+ }
-function EditRow(props: EditRowProps) {
return (
-
event.stopPropagation()}>
-
-
+
)
@@ -174,11 +173,13 @@ export function DialogSelectServer() {
const fetcher = platform.fetch ?? globalThis.fetch
const { defaultUrl, canDefault, setDefault } = useDefaultServer(platform, language)
const { previewStatus } = useServerPreview(fetcher)
- let listRoot: HTMLDivElement | undefined
const [store, setStore] = createStore({
status: {} as Record
,
addServer: {
url: "",
+ name: "",
+ username: "",
+ password: "",
adding: false,
error: "",
showForm: false,
@@ -187,6 +188,9 @@ export function DialogSelectServer() {
editServer: {
id: undefined as string | undefined,
value: "",
+ name: "",
+ username: "",
+ password: "",
error: "",
busy: false,
status: undefined as boolean | undefined,
@@ -196,27 +200,32 @@ export function DialogSelectServer() {
const resetAdd = () => {
setStore("addServer", {
url: "",
+ name: "",
+ username: "",
+ password: "",
+ adding: false,
error: "",
showForm: false,
status: undefined,
})
}
-
const resetEdit = () => {
setStore("editServer", {
id: undefined,
value: "",
+ name: "",
+ username: "",
+ password: "",
error: "",
status: undefined,
busy: false,
})
}
- const replaceServer = (original: ServerConnection.Http, next: string) => {
+ const replaceServer = (original: ServerConnection.Http, next: ServerConnection.Http) => {
const active = server.key
const newConn = server.add(next)
if (!newConn) return
-
const nextActive = active === ServerConnection.key(original) ? ServerConnection.key(newConn) : active
if (nextActive) server.setActive(nextActive)
server.remove(ServerConnection.key(original))
@@ -271,8 +280,8 @@ export function DialogSelectServer() {
async function select(conn: ServerConnection.Any, persist?: boolean) {
if (!persist && store.status[ServerConnection.key(conn)]?.healthy === false) return
dialog.close()
- if (persist) {
- server.add(conn.http.url)
+ if (persist && conn.type === "http") {
+ server.add(conn)
navigate("/")
return
}
@@ -283,21 +292,59 @@ export function DialogSelectServer() {
const handleAddChange = (value: string) => {
if (store.addServer.adding) return
setStore("addServer", { url: value, error: "" })
- void previewStatus(value, (next) => setStore("addServer", { status: next }))
+ void previewStatus(value, store.addServer.username, store.addServer.password, (next) =>
+ setStore("addServer", { status: next }),
+ )
}
- const scrollListToBottom = () => {
- const scroll = listRoot?.querySelector('[data-slot="list-scroll"]')
- if (!scroll) return
- requestAnimationFrame(() => {
- scroll.scrollTop = scroll.scrollHeight
- })
+ const handleAddNameChange = (value: string) => {
+ if (store.addServer.adding) return
+ setStore("addServer", { name: value, error: "" })
+ }
+
+ const handleAddUsernameChange = (value: string) => {
+ if (store.addServer.adding) return
+ setStore("addServer", { username: value, error: "" })
+ void previewStatus(store.addServer.url, value, store.addServer.password, (next) =>
+ setStore("addServer", { status: next }),
+ )
+ }
+
+ const handleAddPasswordChange = (value: string) => {
+ if (store.addServer.adding) return
+ setStore("addServer", { password: value, error: "" })
+ void previewStatus(store.addServer.url, store.addServer.username, value, (next) =>
+ setStore("addServer", { status: next }),
+ )
}
const handleEditChange = (value: string) => {
if (store.editServer.busy) return
setStore("editServer", { value, error: "" })
- void previewStatus(value, (next) => setStore("editServer", { status: next }))
+ void previewStatus(value, store.editServer.username, store.editServer.password, (next) =>
+ setStore("editServer", { status: next }),
+ )
+ }
+
+ const handleEditNameChange = (value: string) => {
+ if (store.editServer.busy) return
+ setStore("editServer", { name: value, error: "" })
+ }
+
+ const handleEditUsernameChange = (value: string) => {
+ if (store.editServer.busy) return
+ setStore("editServer", { username: value, error: "" })
+ void previewStatus(store.editServer.value, value, store.editServer.password, (next) =>
+ setStore("editServer", { status: next }),
+ )
+ }
+
+ const handleEditPasswordChange = (value: string) => {
+ if (store.editServer.busy) return
+ setStore("editServer", { password: value, error: "" })
+ void previewStatus(store.editServer.value, store.editServer.username, value, (next) =>
+ setStore("editServer", { status: next }),
+ )
}
async function handleAdd(value: string) {
@@ -310,16 +357,22 @@ export function DialogSelectServer() {
setStore("addServer", { adding: true, error: "" })
- const result = await checkServerHealth({ url: normalized }, fetcher)
+ const conn: ServerConnection.Http = {
+ type: "http",
+ http: { url: normalized },
+ }
+ if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim()
+ if (store.addServer.username) conn.http.username = store.addServer.username
+ if (store.addServer.password) conn.http.password = store.addServer.password
+ const result = await checkServerHealth(conn.http, fetcher)
setStore("addServer", { adding: false })
-
if (!result.healthy) {
setStore("addServer", { error: language.t("dialog.server.add.error") })
return
}
resetAdd()
- await select({ type: "http", http: { url: normalized } }, true)
+ await select(conn, true)
}
async function handleEdit(original: ServerConnection.Any, value: string) {
@@ -330,53 +383,115 @@ export function DialogSelectServer() {
return
}
- if (normalized === original.http.url) {
+ const name = store.editServer.name.trim() || undefined
+ const username = store.editServer.username || undefined
+ const password = store.editServer.password || undefined
+ const existingName = original.displayName
+ if (
+ normalized === original.http.url &&
+ name === existingName &&
+ username === original.http.username &&
+ password === original.http.password
+ ) {
resetEdit()
return
}
setStore("editServer", { busy: true, error: "" })
- const result = await checkServerHealth({ url: normalized }, fetcher)
+ const conn: ServerConnection.Http = {
+ type: "http",
+ displayName: name,
+ http: { url: normalized, username, password },
+ }
+ const result = await checkServerHealth(conn.http, fetcher)
setStore("editServer", { busy: false })
-
if (!result.healthy) {
setStore("editServer", { error: language.t("dialog.server.add.error") })
return
}
-
- replaceServer(original, normalized)
+ if (normalized === original.http.url) {
+ server.add(conn)
+ } else {
+ replaceServer(original, conn)
+ }
resetEdit()
}
- const handleAddKey = (event: KeyboardEvent) => {
- event.stopPropagation()
- if (event.key !== "Enter" || event.isComposing) return
- event.preventDefault()
- handleAdd(store.addServer.url)
+ const mode = createMemo<"list" | "add" | "edit">(() => {
+ if (store.editServer.id) return "edit"
+ if (store.addServer.showForm) return "add"
+ return "list"
+ })
+
+ const editing = createMemo(() => {
+ if (!store.editServer.id) return
+ return items().find((x) => x.type === "http" && x.http.url === store.editServer.id)
+ })
+
+ const resetForm = () => {
+ resetAdd()
+ resetEdit()
}
- const blurAdd = () => {
- if (!store.addServer.url.trim()) {
- resetAdd()
- return
- }
- handleAdd(store.addServer.url)
+ const startAdd = () => {
+ resetEdit()
+ setStore("addServer", {
+ showForm: true,
+ url: "",
+ name: "",
+ username: "",
+ password: "",
+ error: "",
+ status: undefined,
+ })
}
- const handleEditKey = (event: KeyboardEvent, original: ServerConnection.Any) => {
- event.stopPropagation()
- if (event.key === "Escape") {
- event.preventDefault()
- resetEdit()
+ const startEdit = (conn: ServerConnection.Http) => {
+ resetAdd()
+ setStore("editServer", {
+ id: conn.http.url,
+ value: conn.http.url,
+ name: conn.displayName ?? "",
+ username: conn.http.username ?? "",
+ password: conn.http.password ?? "",
+ error: "",
+ status: store.status[ServerConnection.key(conn)]?.healthy,
+ busy: false,
+ })
+ }
+
+ const submitForm = () => {
+ if (mode() === "add") {
+ void handleAdd(store.addServer.url)
return
}
- if (event.key !== "Enter" || event.isComposing) return
- event.preventDefault()
- handleEdit(original, store.editServer.value)
+ const original = editing()
+ if (!original) return
+ void handleEdit(original, store.editServer.value)
}
+ const isFormMode = createMemo(() => mode() !== "list")
+ const isAddMode = createMemo(() => mode() === "add")
+ const formBusy = createMemo(() => (isAddMode() ? store.addServer.adding : store.editServer.busy))
+
+ const formTitle = createMemo(() => {
+ if (!isFormMode()) return language.t("dialog.server.title")
+ return (
+
+
+ {isAddMode() ? language.t("dialog.server.add.title") : language.t("dialog.server.edit.title")}
+
+ )
+ })
+
+ createEffect(() => {
+ if (!store.editServer.id) return
+ if (editing()) return
+ resetEdit()
+ })
+
async function handleRemove(url: ServerConnection.Key) {
server.remove(url)
if ((await platform.getDefaultServerUrl?.()) === url) {
@@ -385,9 +500,29 @@ export function DialogSelectServer() {
}
return (
-
+
-
(listRoot = el)}>
+
+ }
+ >
{
if (x) select(x)
}}
- onFilter={(value) => {
- if (value && store.addServer.showForm && !store.addServer.adding) {
- resetAdd()
- }
- }}
divider={true}
- class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]:max-h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent [&_[data-slot=list-item-add]]:px-0"
- add={
- store.addServer.showForm
- ? {
- render: () => (
-
- ),
- }
- : undefined
- }
+ class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-raised-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
>
{(i) => {
const key = ServerConnection.key(i)
return (
-
-
handleEditKey(event, i)}
- onBlur={() => handleEdit(i, store.editServer.value)}
- />
- }
- >
-
-
- {language.t("dialog.server.status.default")}
-
-
- }
- />
-
-
-
-
- {language.t("dialog.server.current")}
+
+
+
+
+
+
+ {language.t("dialog.server.status.default")}
+
+ }
+ showCredentials
+ />
+
+
+
+
-
-
- e.stopPropagation()}
- onPointerDown={(e: PointerEvent) => e.stopPropagation()}
- />
-
-
- {
- setStore("editServer", {
- id: i.http.url,
- value: i.http.url,
- error: "",
- status: store.status[ServerConnection.key(i)]?.healthy,
- })
- }}
- >
- {language.t("dialog.server.menu.edit")}
-
-
- setDefault(i.http.url)}>
-
- {language.t("dialog.server.menu.default")}
-
-
-
-
- setDefault(null)}>
-
- {language.t("dialog.server.menu.defaultRemove")}
-
-
-
-
- handleRemove(ServerConnection.key(i))}
- class="text-text-on-critical-base hover:bg-surface-critical-weak"
- >
+
+
+ e.stopPropagation()}
+ onPointerDown={(e: PointerEvent) => e.stopPropagation()}
+ />
+
+
+ {
+ if (i.type !== "http") return
+ startEdit(i)
+ }}
+ >
+ {language.t("dialog.server.menu.edit")}
+
+
+ setDefault(i.http.url)}>
- {language.t("dialog.server.menu.delete")}
+ {language.t("dialog.server.menu.default")}
-
-
-
-
-
-
+
+
+ setDefault(null)}>
+
+ {language.t("dialog.server.menu.defaultRemove")}
+
+
+
+
+ handleRemove(ServerConnection.key(i))}
+ class="text-text-on-critical-base hover:bg-surface-critical-weak"
+ >
+ {language.t("dialog.server.menu.delete")}
+
+
+
+
+
+
)
}}
-
+
- {
- setStore("addServer", { showForm: true, url: "", error: "" })
- scrollListToBottom()
- }}
- class="py-1.5 pl-1.5 pr-3 flex items-center gap-1.5"
+
+ {language.t("dialog.server.add.button")}
+
+ }
>
- {store.addServer.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
-
+
+ {formBusy()
+ ? language.t("dialog.server.add.checking")
+ : isAddMode()
+ ? language.t("dialog.server.add.button")
+ : language.t("common.save")}
+
+
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx
index cec0943542..930832fb65 100644
--- a/packages/app/src/components/file-tree.tsx
+++ b/packages/app/src/components/file-tree.tsx
@@ -3,7 +3,6 @@ import { encodeFilePath } from "@/context/file/path"
import { Collapsible } from "@opencode-ai/ui/collapsible"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
-import { Tooltip } from "@opencode-ai/ui/tooltip"
import {
createEffect,
createMemo,
@@ -192,59 +191,6 @@ const FileTreeNode = (
)
}
-const FileTreeNodeTooltip = (props: { enabled: boolean; node: FileNode; kind?: Kind; children: JSXElement }) => {
- if (!props.enabled) return props.children
-
- const parts = props.node.path.split("/")
- const leaf = parts[parts.length - 1] ?? props.node.path
- const head = parts.slice(0, -1).join("/")
- const prefix = head ? `${head}/` : ""
- const label =
- props.kind === "add"
- ? "Additions"
- : props.kind === "del"
- ? "Deletions"
- : props.kind === "mix"
- ? "Modifications"
- : undefined
-
- return (
-
-
- {prefix}
-
- {leaf}
-
- {(text) => (
- <>
- •
- {text()}
- >
- )}
-
-
- <>
- •
- Ignored
- >
-
-
- }
- >
- {props.children}
-
- )
-}
-
export default function FileTree(props: {
path: string
class?: string
@@ -255,7 +201,6 @@ export default function FileTree(props: {
modified?: readonly string[]
kinds?: ReadonlyMap
draggable?: boolean
- tooltip?: boolean
onFileClick?: (file: FileNode) => void
_filter?: Filter
@@ -267,7 +212,6 @@ export default function FileTree(props: {
const file = useFile()
const level = props.level ?? 0
const draggable = () => props.draggable ?? true
- const tooltip = () => props.tooltip ?? true
const key = (p: string) =>
file
@@ -381,12 +325,6 @@ export default function FileTree(props: {
),
)
- createEffect(() => {
- const dir = file.tree.state(props.path)
- if (!shouldListExpanded({ level, dir })) return
- void file.tree.list(props.path)
- })
-
const nodes = createMemo(() => {
const nodes = file.tree.children(props.path)
const current = filter()
@@ -467,21 +405,19 @@ export default function FileTree(props: {
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
>
-
-
-
-
-
-
-
+
+
+
+
+
-
- props.onFileClick?.(node)}
- >
-
-
-
+ props.onFileClick?.(node)}
+ >
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
)
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index adfd592f8d..532edd3bcd 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -1,9 +1,10 @@
import { useFilteredList } from "@opencode-ai/ui/hooks"
+import { useSpring } from "@opencode-ai/ui/motion-spring"
import { createEffect, on, Component, Show, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import { createFocusSignal } from "@solid-primitives/active-element"
import { useLocal } from "@/context/local"
-import { useFile } from "@/context/file"
+import { selectionFromLines, type SelectedLineRange, useFile } from "@/context/file"
import {
ContentPart,
DEFAULT_PROMPT,
@@ -23,7 +24,6 @@ import { Button } from "@opencode-ai/ui/button"
import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
import { Icon } from "@opencode-ai/ui/icon"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
-import type { IconName } from "@opencode-ai/ui/icons/provider"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Select } from "@opencode-ai/ui/select"
@@ -43,6 +43,9 @@ import {
canNavigateHistoryAtCursor,
navigatePromptHistory,
prependHistoryEntry,
+ type PromptHistoryComment,
+ type PromptHistoryEntry,
+ type PromptHistoryStoredEntry,
promptLength,
} from "./prompt-input/history"
import { createPromptSubmit } from "./prompt-input/submit"
@@ -170,12 +173,29 @@ export const PromptInput: Component
= (props) => {
const focus = { file: item.path, id: item.commentID }
comments.setActive(focus)
+ const queueCommentFocus = (attempts = 6) => {
+ const schedule = (left: number) => {
+ requestAnimationFrame(() => {
+ comments.setFocus({ ...focus })
+ if (left <= 0) return
+ requestAnimationFrame(() => {
+ const current = comments.focus()
+ if (!current) return
+ if (current.file !== focus.file || current.id !== focus.id) return
+ schedule(left - 1)
+ })
+ })
+ }
+
+ schedule(attempts)
+ }
+
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
if (wantsReview) {
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("changes")
tabs().setActive("review")
- requestAnimationFrame(() => comments.setFocus(focus))
+ queueCommentFocus()
return
}
@@ -183,8 +203,8 @@ export const PromptInput: Component = (props) => {
layout.fileTree.setTab("all")
const tab = files.tab(item.path)
tabs().open(tab)
- files.load(item.path)
- requestAnimationFrame(() => comments.setFocus(focus))
+ tabs().setActive(tab)
+ Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
}
const recent = createMemo(() => {
@@ -219,7 +239,7 @@ export const PromptInput: Component = (props) => {
const [store, setStore] = createStore<{
popover: "at" | "slash" | null
historyIndex: number
- savedPrompt: Prompt | null
+ savedPrompt: PromptHistoryEntry | null
placeholder: number
draggingType: "image" | "@mention" | null
mode: "normal" | "shell"
@@ -227,13 +247,15 @@ export const PromptInput: Component = (props) => {
}>({
popover: null,
historyIndex: -1,
- savedPrompt: null,
+ savedPrompt: null as PromptHistoryEntry | null,
placeholder: Math.floor(Math.random() * EXAMPLES.length),
draggingType: null,
mode: "normal",
applyingHistory: false,
})
+ const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 })
+
const commentCount = createMemo(() => {
if (store.mode === "shell") return 0
return prompt.context.items().filter((item) => !!item.comment?.trim()).length
@@ -256,7 +278,7 @@ export const PromptInput: Component = (props) => {
const [history, setHistory] = persisted(
Persist.global("prompt-history", ["prompt-history.v1"]),
createStore<{
- entries: Prompt[]
+ entries: PromptHistoryStoredEntry[]
}>({
entries: [],
}),
@@ -264,7 +286,7 @@ export const PromptInput: Component = (props) => {
const [shellHistory, setShellHistory] = persisted(
Persist.global("prompt-history-shell", ["prompt-history-shell.v1"]),
createStore<{
- entries: Prompt[]
+ entries: PromptHistoryStoredEntry[]
}>({
entries: [],
}),
@@ -282,9 +304,66 @@ export const PromptInput: Component = (props) => {
}),
)
- const applyHistoryPrompt = (p: Prompt, position: "start" | "end") => {
+ const historyComments = () => {
+ const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const))
+ return prompt.context.items().flatMap((item) => {
+ if (item.type !== "file") return []
+ const comment = item.comment?.trim()
+ if (!comment) return []
+
+ const selection = item.commentID ? byID.get(`${item.path}\n${item.commentID}`)?.selection : undefined
+ const nextSelection =
+ selection ??
+ (item.selection
+ ? ({
+ start: item.selection.startLine,
+ end: item.selection.endLine,
+ } satisfies SelectedLineRange)
+ : undefined)
+ if (!nextSelection) return []
+
+ return [
+ {
+ id: item.commentID ?? item.key,
+ path: item.path,
+ selection: { ...nextSelection },
+ comment,
+ time: item.commentID ? (byID.get(`${item.path}\n${item.commentID}`)?.time ?? Date.now()) : Date.now(),
+ origin: item.commentOrigin,
+ preview: item.preview,
+ } satisfies PromptHistoryComment,
+ ]
+ })
+ }
+
+ const applyHistoryComments = (items: PromptHistoryComment[]) => {
+ comments.replace(
+ items.map((item) => ({
+ id: item.id,
+ file: item.path,
+ selection: { ...item.selection },
+ comment: item.comment,
+ time: item.time,
+ })),
+ )
+ prompt.context.replaceComments(
+ items.map((item) => ({
+ type: "file" as const,
+ path: item.path,
+ selection: selectionFromLines(item.selection),
+ comment: item.comment,
+ commentID: item.id,
+ commentOrigin: item.origin,
+ preview: item.preview,
+ })),
+ )
+ }
+
+ const applyHistoryPrompt = (entry: PromptHistoryEntry, position: "start" | "end") => {
+ const p = entry.prompt
const length = position === "start" ? 0 : promptLength(p)
setStore("applyingHistory", true)
+ applyHistoryComments(entry.comments)
prompt.set(p, length)
requestAnimationFrame(() => {
editorRef.focus()
@@ -515,7 +594,6 @@ export const PromptInput: Component = (props) => {
setActive: setSlashActive,
onInput: slashOnInput,
onKeyDown: slashOnKeyDown,
- refetch: slashRefetch,
} = useFilteredList({
items: slashCommands,
key: (x) => x?.id,
@@ -572,14 +650,6 @@ export const PromptInput: Component = (props) => {
}
}
- createEffect(
- on(
- () => sync.data.command,
- () => slashRefetch(),
- { defer: true },
- ),
- )
-
// Auto-scroll active command into view when navigating with keyboard
createEffect(() => {
const activeId = slashActive()
@@ -846,7 +916,7 @@ export const PromptInput: Component = (props) => {
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
const currentHistory = mode === "shell" ? shellHistory : history
const setCurrentHistory = mode === "shell" ? setShellHistory : setHistory
- const next = prependHistoryEntry(currentHistory.entries, prompt)
+ const next = prependHistoryEntry(currentHistory.entries, prompt, mode === "shell" ? [] : historyComments())
if (next === currentHistory.entries) return
setCurrentHistory("entries", next)
}
@@ -857,12 +927,13 @@ export const PromptInput: Component = (props) => {
entries: store.mode === "shell" ? shellHistory.entries : history.entries,
historyIndex: store.historyIndex,
currentPrompt: prompt.current(),
+ currentComments: historyComments(),
savedPrompt: store.savedPrompt,
})
if (!result.handled) return false
setStore("historyIndex", result.historyIndex)
setStore("savedPrompt", result.savedPrompt)
- applyHistoryPrompt(result.prompt, result.cursor)
+ applyHistoryPrompt(result.entry, result.cursor)
return true
}
@@ -879,10 +950,18 @@ export const PromptInput: Component = (props) => {
readClipboardImage: platform.readClipboardImage,
})
+ const variants = createMemo(() => ["default", ...local.model.variant.list()])
+ const accepting = createMemo(() => {
+ const id = params.id
+ if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
+ return permission.isAutoAccepting(id, sdk.directory)
+ })
+
const { abort, handleSubmit } = createPromptSubmit({
info,
imageAttachments,
commentCount,
+ autoAccept: () => accepting(),
mode: () => store.mode,
working,
editor: () => editorRef,
@@ -1047,8 +1126,6 @@ export const PromptInput: Component = (props) => {
}
}
- const variants = createMemo(() => ["default", ...local.model.variant.list()])
-
return (
= (props) => {
aria-multiline="true"
aria-label={placeholder()}
contenteditable="true"
- autocapitalize="off"
- autocorrect="off"
- spellcheck={false}
+ autocapitalize={store.mode === "normal" ? "sentences" : "off"}
+ autocorrect={store.mode === "normal" ? "on" : "off"}
+ spellcheck={store.mode === "normal"}
onInput={handleInput}
onPaste={handlePaste}
onCompositionStart={() => setComposing(true)}
@@ -1168,10 +1245,9 @@ export const PromptInput: Component = (props) => {
0.5 ? "auto" : "none",
}}
>
= (props) => {
type="button"
variant="ghost"
class="size-8 p-0"
+ style={{
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ }}
onClick={pick}
disabled={store.mode !== "normal"}
tabIndex={store.mode === "normal" ? undefined : -1}
@@ -1221,60 +1302,78 @@ export const PromptInput: Component = (props) => {
icon={working() ? "stop" : "arrow-up"}
variant="primary"
class="size-8"
+ style={{
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ }}
aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
/>
-
-
-
-
- permission.toggleAutoAccept(params.id!, sdk.directory)}
- classList={{
- "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
- "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
- "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
- }}
- aria-label={
- permission.isAutoAccepting(params.id!, sdk.directory)
- ? language.t("command.permissions.autoaccept.disable")
- : language.t("command.permissions.autoaccept.enable")
+
+
+
+ {
+ if (!params.id) {
+ permission.toggleAutoAcceptDirectory(sdk.directory)
+ return
}
- aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
- >
-
-
-
-
+ permission.toggleAutoAccept(params.id, sdk.directory)
+ }}
+ classList={{
+ "size-6 flex items-center justify-center": true,
+ "text-text-base": !accepting(),
+ "hover:bg-surface-success-base": accepting(),
+ }}
+ aria-label={
+ accepting()
+ ? language.t("command.permissions.autoaccept.disable")
+ : language.t("command.permissions.autoaccept.enable")
+ }
+ aria-pressed={accepting()}
+ >
+
+
+
-
+
-
-
-
-
{language.t("prompt.mode.shell")}
-
-
-
-
+
+
+
{language.t("prompt.mode.shell")}
+
+
+
= (props) => {
onSelect={local.agent.set}
class="capitalize max-w-[160px]"
valueClass="truncate text-13-regular"
- triggerStyle={{ height: "28px" }}
+ triggerStyle={{
+ height: "28px",
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ "pointer-events": buttonsSpring() > 0.5 ? "auto" : "none",
+ }}
variant="ghost"
/>
@@ -1306,12 +1411,18 @@ export const PromptInput: Component
= (props) => {
variant="ghost"
size="normal"
class="min-w-0 max-w-[320px] text-13-regular group"
- style={{ height: "28px" }}
+ style={{
+ height: "28px",
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ "pointer-events": buttonsSpring() > 0.5 ? "auto" : "none",
+ }}
onClick={() => dialog.show(() => )}
>
@@ -1335,13 +1446,19 @@ export const PromptInput: Component = (props) => {
triggerProps={{
variant: "ghost",
size: "normal",
- style: { height: "28px" },
+ style: {
+ height: "28px",
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ "pointer-events": buttonsSpring() > 0.5 ? "auto" : "none",
+ },
class: "min-w-0 max-w-[320px] text-13-regular group",
}}
>
@@ -1367,11 +1484,17 @@ export const PromptInput: Component = (props) => {
onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)}
class="capitalize max-w-[160px]"
valueClass="truncate text-13-regular"
- triggerStyle={{ height: "28px" }}
+ triggerStyle={{
+ height: "28px",
+ opacity: buttonsSpring(),
+ transform: `scale(${0.95 + buttonsSpring() * 0.05})`,
+ filter: `blur(${(1 - buttonsSpring()) * 2}px)`,
+ "pointer-events": buttonsSpring() > 0.5 ? "auto" : "none",
+ }}
variant="ghost"
/>
-
+
{
result.requestParts.some((part) => part.type === "file" && part.url.startsWith("file:///repo/src/foo.ts")),
).toBe(true)
expect(result.requestParts.some((part) => part.type === "text" && part.synthetic)).toBe(true)
+ expect(
+ result.requestParts.some(
+ (part) =>
+ part.type === "text" &&
+ part.synthetic &&
+ part.metadata?.opencodeComment &&
+ (part.metadata.opencodeComment as { comment?: string }).comment === "check this",
+ ),
+ ).toBe(true)
expect(result.optimisticParts).toHaveLength(result.requestParts.length)
expect(result.optimisticParts.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_1")).toBe(true)
diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts
index 0cc54dc2b7..4146fb4847 100644
--- a/packages/app/src/components/prompt-input/build-request-parts.ts
+++ b/packages/app/src/components/prompt-input/build-request-parts.ts
@@ -4,6 +4,7 @@ import type { FileSelection } from "@/context/file"
import { encodeFilePath } from "@/context/file/path"
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
import { Identifier } from "@/utils/id"
+import { createCommentMetadata, formatCommentNote } from "@/utils/comment-note"
type PromptRequestPart = (TextPartInput | FilePartInput | AgentPartInput) & { id: string }
@@ -41,18 +42,6 @@ const fileQuery = (selection: FileSelection | undefined) =>
const isFileAttachment = (part: Prompt[number]): part is FileAttachmentPart => part.type === "file"
const isAgentAttachment = (part: Prompt[number]): part is AgentPart => part.type === "agent"
-const commentNote = (path: string, selection: FileSelection | undefined, comment: string) => {
- const start = selection ? Math.min(selection.startLine, selection.endLine) : undefined
- const end = selection ? Math.max(selection.startLine, selection.endLine) : undefined
- const range =
- start === undefined || end === undefined
- ? "this file"
- : start === end
- ? `line ${start}`
- : `lines ${start} through ${end}`
- return `The user made the following comment regarding ${range} of ${path}: ${comment}`
-}
-
const toOptimisticPart = (part: PromptRequestPart, sessionID: string, messageID: string): Part => {
if (part.type === "text") {
return {
@@ -153,8 +142,15 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
{
id: Identifier.ascending("part"),
type: "text",
- text: commentNote(item.path, item.selection, comment),
+ text: formatCommentNote({ path: item.path, selection: item.selection, comment }),
synthetic: true,
+ metadata: createCommentMetadata({
+ path: item.path,
+ selection: item.selection,
+ comment,
+ preview: item.preview,
+ origin: item.commentOrigin,
+ }),
} satisfies PromptRequestPart,
filePart,
]
diff --git a/packages/app/src/components/prompt-input/history.test.ts b/packages/app/src/components/prompt-input/history.test.ts
index b7a4f896b8..37b5ce1962 100644
--- a/packages/app/src/components/prompt-input/history.test.ts
+++ b/packages/app/src/components/prompt-input/history.test.ts
@@ -3,25 +3,42 @@ import type { Prompt } from "@/context/prompt"
import {
canNavigateHistoryAtCursor,
clonePromptParts,
+ normalizePromptHistoryEntry,
navigatePromptHistory,
prependHistoryEntry,
promptLength,
+ type PromptHistoryComment,
} from "./history"
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
const text = (value: string): Prompt => [{ type: "text", content: value, start: 0, end: value.length }]
+const comment = (id: string, value = "note"): PromptHistoryComment => ({
+ id,
+ path: "src/a.ts",
+ selection: { start: 2, end: 4 },
+ comment: value,
+ time: 1,
+ origin: "review",
+ preview: "const a = 1",
+})
describe("prompt-input history", () => {
test("prependHistoryEntry skips empty prompt and deduplicates consecutive entries", () => {
const first = prependHistoryEntry([], DEFAULT_PROMPT)
expect(first).toEqual([])
+ const commentsOnly = prependHistoryEntry([], DEFAULT_PROMPT, [comment("c1")])
+ expect(commentsOnly).toHaveLength(1)
+
const withOne = prependHistoryEntry([], text("hello"))
expect(withOne).toHaveLength(1)
const deduped = prependHistoryEntry(withOne, text("hello"))
expect(deduped).toBe(withOne)
+
+ const dedupedComments = prependHistoryEntry(commentsOnly, DEFAULT_PROMPT, [comment("c1")])
+ expect(dedupedComments).toBe(commentsOnly)
})
test("navigatePromptHistory restores saved prompt when moving down from newest", () => {
@@ -31,24 +48,57 @@ describe("prompt-input history", () => {
entries,
historyIndex: -1,
currentPrompt: text("draft"),
+ currentComments: [comment("draft")],
savedPrompt: null,
})
expect(up.handled).toBe(true)
if (!up.handled) throw new Error("expected handled")
expect(up.historyIndex).toBe(0)
expect(up.cursor).toBe("start")
+ expect(up.entry.comments).toEqual([])
const down = navigatePromptHistory({
direction: "down",
entries,
historyIndex: up.historyIndex,
currentPrompt: text("ignored"),
+ currentComments: [],
savedPrompt: up.savedPrompt,
})
expect(down.handled).toBe(true)
if (!down.handled) throw new Error("expected handled")
expect(down.historyIndex).toBe(-1)
- expect(down.prompt[0]?.type === "text" ? down.prompt[0].content : "").toBe("draft")
+ expect(down.entry.prompt[0]?.type === "text" ? down.entry.prompt[0].content : "").toBe("draft")
+ expect(down.entry.comments).toEqual([comment("draft")])
+ })
+
+ test("navigatePromptHistory keeps entry comments when moving through history", () => {
+ const entries = [
+ {
+ prompt: text("with comment"),
+ comments: [comment("c1")],
+ },
+ ]
+
+ const up = navigatePromptHistory({
+ direction: "up",
+ entries,
+ historyIndex: -1,
+ currentPrompt: text("draft"),
+ currentComments: [],
+ savedPrompt: null,
+ })
+
+ expect(up.handled).toBe(true)
+ if (!up.handled) throw new Error("expected handled")
+ expect(up.entry.prompt[0]?.type === "text" ? up.entry.prompt[0].content : "").toBe("with comment")
+ expect(up.entry.comments).toEqual([comment("c1")])
+ })
+
+ test("normalizePromptHistoryEntry supports legacy prompt arrays", () => {
+ const entry = normalizePromptHistoryEntry(text("legacy"))
+ expect(entry.prompt[0]?.type === "text" ? entry.prompt[0].content : "").toBe("legacy")
+ expect(entry.comments).toEqual([])
})
test("helpers clone prompt and count text content length", () => {
diff --git a/packages/app/src/components/prompt-input/history.ts b/packages/app/src/components/prompt-input/history.ts
index c279a3ed56..de62653211 100644
--- a/packages/app/src/components/prompt-input/history.ts
+++ b/packages/app/src/components/prompt-input/history.ts
@@ -1,9 +1,27 @@
import type { Prompt } from "@/context/prompt"
+import type { SelectedLineRange } from "@/context/file"
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export const MAX_HISTORY = 100
+export type PromptHistoryComment = {
+ id: string
+ path: string
+ selection: SelectedLineRange
+ comment: string
+ time: number
+ origin?: "review" | "file"
+ preview?: string
+}
+
+export type PromptHistoryEntry = {
+ prompt: Prompt
+ comments: PromptHistoryComment[]
+}
+
+export type PromptHistoryStoredEntry = Prompt | PromptHistoryEntry
+
export function canNavigateHistoryAtCursor(direction: "up" | "down", text: string, cursor: number, inHistory = false) {
const position = Math.max(0, Math.min(cursor, text.length))
const atStart = position === 0
@@ -25,29 +43,82 @@ export function clonePromptParts(prompt: Prompt): Prompt {
})
}
+function cloneSelection(selection: SelectedLineRange): SelectedLineRange {
+ return {
+ start: selection.start,
+ end: selection.end,
+ ...(selection.side ? { side: selection.side } : {}),
+ ...(selection.endSide ? { endSide: selection.endSide } : {}),
+ }
+}
+
+export function clonePromptHistoryComments(comments: PromptHistoryComment[]) {
+ return comments.map((comment) => ({
+ ...comment,
+ selection: cloneSelection(comment.selection),
+ }))
+}
+
+export function normalizePromptHistoryEntry(entry: PromptHistoryStoredEntry): PromptHistoryEntry {
+ if (Array.isArray(entry)) {
+ return {
+ prompt: clonePromptParts(entry),
+ comments: [],
+ }
+ }
+ return {
+ prompt: clonePromptParts(entry.prompt),
+ comments: clonePromptHistoryComments(entry.comments),
+ }
+}
+
export function promptLength(prompt: Prompt) {
return prompt.reduce((len, part) => len + ("content" in part ? part.content.length : 0), 0)
}
-export function prependHistoryEntry(entries: Prompt[], prompt: Prompt, max = MAX_HISTORY) {
+export function prependHistoryEntry(
+ entries: PromptHistoryStoredEntry[],
+ prompt: Prompt,
+ comments: PromptHistoryComment[] = [],
+ max = MAX_HISTORY,
+) {
const text = prompt
.map((part) => ("content" in part ? part.content : ""))
.join("")
.trim()
const hasImages = prompt.some((part) => part.type === "image")
- if (!text && !hasImages) return entries
+ const hasComments = comments.some((comment) => !!comment.comment.trim())
+ if (!text && !hasImages && !hasComments) return entries
- const entry = clonePromptParts(prompt)
+ const entry = {
+ prompt: clonePromptParts(prompt),
+ comments: clonePromptHistoryComments(comments),
+ } satisfies PromptHistoryEntry
const last = entries[0]
if (last && isPromptEqual(last, entry)) return entries
return [entry, ...entries].slice(0, max)
}
-function isPromptEqual(promptA: Prompt, promptB: Prompt) {
- if (promptA.length !== promptB.length) return false
- for (let i = 0; i < promptA.length; i++) {
- const partA = promptA[i]
- const partB = promptB[i]
+function isCommentEqual(commentA: PromptHistoryComment, commentB: PromptHistoryComment) {
+ return (
+ commentA.path === commentB.path &&
+ commentA.comment === commentB.comment &&
+ commentA.origin === commentB.origin &&
+ commentA.preview === commentB.preview &&
+ commentA.selection.start === commentB.selection.start &&
+ commentA.selection.end === commentB.selection.end &&
+ commentA.selection.side === commentB.selection.side &&
+ commentA.selection.endSide === commentB.selection.endSide
+ )
+}
+
+function isPromptEqual(promptA: PromptHistoryStoredEntry, promptB: PromptHistoryStoredEntry) {
+ const entryA = normalizePromptHistoryEntry(promptA)
+ const entryB = normalizePromptHistoryEntry(promptB)
+ if (entryA.prompt.length !== entryB.prompt.length) return false
+ for (let i = 0; i < entryA.prompt.length; i++) {
+ const partA = entryA.prompt[i]
+ const partB = entryB.prompt[i]
if (partA.type !== partB.type) return false
if (partA.type === "text" && partA.content !== (partB.type === "text" ? partB.content : "")) return false
if (partA.type === "file") {
@@ -67,28 +138,35 @@ function isPromptEqual(promptA: Prompt, promptB: Prompt) {
if (partA.type === "agent" && partA.name !== (partB.type === "agent" ? partB.name : "")) return false
if (partA.type === "image" && partA.id !== (partB.type === "image" ? partB.id : "")) return false
}
+ if (entryA.comments.length !== entryB.comments.length) return false
+ for (let i = 0; i < entryA.comments.length; i++) {
+ const commentA = entryA.comments[i]
+ const commentB = entryB.comments[i]
+ if (!commentA || !commentB || !isCommentEqual(commentA, commentB)) return false
+ }
return true
}
type HistoryNavInput = {
direction: "up" | "down"
- entries: Prompt[]
+ entries: PromptHistoryStoredEntry[]
historyIndex: number
currentPrompt: Prompt
- savedPrompt: Prompt | null
+ currentComments: PromptHistoryComment[]
+ savedPrompt: PromptHistoryEntry | null
}
type HistoryNavResult =
| {
handled: false
historyIndex: number
- savedPrompt: Prompt | null
+ savedPrompt: PromptHistoryEntry | null
}
| {
handled: true
historyIndex: number
- savedPrompt: Prompt | null
- prompt: Prompt
+ savedPrompt: PromptHistoryEntry | null
+ entry: PromptHistoryEntry
cursor: "start" | "end"
}
@@ -103,22 +181,27 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
}
if (input.historyIndex === -1) {
+ const entry = normalizePromptHistoryEntry(input.entries[0])
return {
handled: true,
historyIndex: 0,
- savedPrompt: clonePromptParts(input.currentPrompt),
- prompt: input.entries[0],
+ savedPrompt: {
+ prompt: clonePromptParts(input.currentPrompt),
+ comments: clonePromptHistoryComments(input.currentComments),
+ },
+ entry,
cursor: "start",
}
}
if (input.historyIndex < input.entries.length - 1) {
const next = input.historyIndex + 1
+ const entry = normalizePromptHistoryEntry(input.entries[next])
return {
handled: true,
historyIndex: next,
savedPrompt: input.savedPrompt,
- prompt: input.entries[next],
+ entry,
cursor: "start",
}
}
@@ -132,11 +215,12 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
if (input.historyIndex > 0) {
const next = input.historyIndex - 1
+ const entry = normalizePromptHistoryEntry(input.entries[next])
return {
handled: true,
historyIndex: next,
savedPrompt: input.savedPrompt,
- prompt: input.entries[next],
+ entry,
cursor: "end",
}
}
@@ -147,7 +231,7 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
handled: true,
historyIndex: -1,
savedPrompt: null,
- prompt: input.savedPrompt,
+ entry: input.savedPrompt,
cursor: "end",
}
}
@@ -156,7 +240,10 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
handled: true,
historyIndex: -1,
savedPrompt: null,
- prompt: DEFAULT_PROMPT,
+ entry: {
+ prompt: DEFAULT_PROMPT,
+ comments: [],
+ },
cursor: "end",
}
}
diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts
index c3d6a92813..4109417d2b 100644
--- a/packages/app/src/components/prompt-input/submit.test.ts
+++ b/packages/app/src/components/prompt-input/submit.test.ts
@@ -5,10 +5,20 @@ let createPromptSubmit: typeof import("./submit").createPromptSubmit
const createdClients: string[] = []
const createdSessions: string[] = []
+const enabledAutoAccept: Array<{ sessionID: string; directory: string }> = []
+const optimistic: Array<{
+ message: {
+ agent: string
+ model: { providerID: string; modelID: string }
+ variant?: string
+ }
+}> = []
const sentShell: string[] = []
const syncedDirectories: string[] = []
+let params: { id?: string } = {}
let selected = "/repo/worktree-a"
+let variant: string | undefined
const promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
@@ -25,6 +35,7 @@ const clientFor = (directory: string) => {
return { data: undefined }
},
prompt: async () => ({ data: undefined }),
+ promptAsync: async () => ({ data: undefined }),
command: async () => ({ data: undefined }),
abort: async () => ({ data: undefined }),
},
@@ -39,7 +50,7 @@ beforeAll(async () => {
mock.module("@solidjs/router", () => ({
useNavigate: () => () => undefined,
- useParams: () => ({}),
+ useParams: () => params,
}))
mock.module("@opencode-ai/sdk/v2/client", () => ({
@@ -61,7 +72,7 @@ beforeAll(async () => {
useLocal: () => ({
model: {
current: () => ({ id: "model", provider: { id: "provider" } }),
- variant: { current: () => undefined },
+ variant: { current: () => variant },
},
agent: {
current: () => ({ name: "agent" }),
@@ -69,6 +80,14 @@ beforeAll(async () => {
}),
}))
+ mock.module("@/context/permission", () => ({
+ usePermission: () => ({
+ enableAutoAccept(sessionID: string, directory: string) {
+ enabledAutoAccept.push({ sessionID, directory })
+ },
+ }),
+ }))
+
mock.module("@/context/prompt", () => ({
usePrompt: () => ({
current: () => promptValue,
@@ -109,7 +128,11 @@ beforeAll(async () => {
data: { command: [] },
session: {
optimistic: {
- add: () => undefined,
+ add: (value: {
+ message: { agent: string; model: { providerID: string; modelID: string }; variant?: string }
+ }) => {
+ optimistic.push(value)
+ },
remove: () => undefined,
},
},
@@ -145,9 +168,13 @@ beforeAll(async () => {
beforeEach(() => {
createdClients.length = 0
createdSessions.length = 0
+ enabledAutoAccept.length = 0
+ optimistic.length = 0
+ params = {}
sentShell.length = 0
syncedDirectories.length = 0
selected = "/repo/worktree-a"
+ variant = undefined
})
describe("prompt submit worktree selection", () => {
@@ -156,6 +183,7 @@ describe("prompt submit worktree selection", () => {
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
+ autoAccept: () => false,
mode: () => "shell",
working: () => false,
editor: () => undefined,
@@ -181,4 +209,66 @@ describe("prompt submit worktree selection", () => {
expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
})
+
+ test("applies auto-accept to newly created sessions", async () => {
+ const submit = createPromptSubmit({
+ info: () => undefined,
+ imageAttachments: () => [],
+ commentCount: () => 0,
+ autoAccept: () => true,
+ mode: () => "shell",
+ working: () => false,
+ editor: () => undefined,
+ queueScroll: () => undefined,
+ promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
+ addToHistory: () => undefined,
+ resetHistoryNavigation: () => undefined,
+ setMode: () => undefined,
+ setPopover: () => undefined,
+ newSessionWorktree: () => selected,
+ onNewSessionWorktreeReset: () => undefined,
+ onSubmit: () => undefined,
+ })
+
+ const event = { preventDefault: () => undefined } as unknown as Event
+
+ await submit.handleSubmit(event)
+
+ expect(enabledAutoAccept).toEqual([{ sessionID: "session-1", directory: "/repo/worktree-a" }])
+ })
+
+ test("includes the selected variant on optimistic prompts", async () => {
+ params = { id: "session-1" }
+ variant = "high"
+
+ const submit = createPromptSubmit({
+ info: () => ({ id: "session-1" }),
+ imageAttachments: () => [],
+ commentCount: () => 0,
+ autoAccept: () => false,
+ mode: () => "normal",
+ working: () => false,
+ editor: () => undefined,
+ queueScroll: () => undefined,
+ promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
+ addToHistory: () => undefined,
+ resetHistoryNavigation: () => undefined,
+ setMode: () => undefined,
+ setPopover: () => undefined,
+ onSubmit: () => undefined,
+ })
+
+ const event = { preventDefault: () => undefined } as unknown as Event
+
+ await submit.handleSubmit(event)
+
+ expect(optimistic).toHaveLength(1)
+ expect(optimistic[0]).toMatchObject({
+ message: {
+ agent: "agent",
+ model: { providerID: "provider", modelID: "model" },
+ variant: "high",
+ },
+ })
+ })
})
diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts
index a7ff39e091..fee6b070d9 100644
--- a/packages/app/src/components/prompt-input/submit.ts
+++ b/packages/app/src/components/prompt-input/submit.ts
@@ -8,6 +8,7 @@ import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useLocal } from "@/context/local"
+import { usePermission } from "@/context/permission"
import { type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk"
import { useSync } from "@/context/sync"
@@ -15,6 +16,7 @@ import { Identifier } from "@/utils/id"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { buildRequestParts } from "./build-request-parts"
import { setCursorPosition } from "./editor-dom"
+import { formatServerError } from "@/utils/server-errors"
type PendingPrompt = {
abort: AbortController
@@ -27,6 +29,7 @@ type PromptSubmitInput = {
info: Accessor<{ id: string } | undefined>
imageAttachments: Accessor
commentCount: Accessor
+ autoAccept: Accessor
mode: Accessor<"normal" | "shell">
working: Accessor
editor: () => HTMLDivElement | undefined
@@ -56,6 +59,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const sync = useSync()
const globalSync = useGlobalSync()
const local = useLocal()
+ const permission = usePermission()
const prompt = usePrompt()
const layout = useLayout()
const language = useLanguage()
@@ -140,6 +144,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const projectDirectory = sdk.directory
const isNewSession = !params.id
+ const shouldAutoAccept = isNewSession && input.autoAccept()
const worktreeSelection = input.newSessionWorktree?.() || "main"
let sessionDirectory = projectDirectory
@@ -197,6 +202,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
return undefined
})
if (session) {
+ if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory)
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
}
@@ -281,7 +287,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
.catch((err) => {
showToast({
title: language.t("prompt.toast.commandSendFailed.title"),
- description: errorMessage(err),
+ description: formatServerError(err, language.t, language.t("common.requestFailed")),
})
restoreInput()
})
@@ -310,6 +316,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
time: { created: Date.now() },
agent,
model,
+ variant,
}
const addOptimisticMessage = () =>
diff --git a/packages/app/src/components/server/server-row.tsx b/packages/app/src/components/server/server-row.tsx
index 12dcebfa97..5bb290ec30 100644
--- a/packages/app/src/components/server/server-row.tsx
+++ b/packages/app/src/components/server/server-row.tsx
@@ -1,5 +1,6 @@
import { Tooltip } from "@opencode-ai/ui/tooltip"
import {
+ children,
createEffect,
createMemo,
createSignal,
@@ -9,7 +10,7 @@ import {
type ParentProps,
Show,
} from "solid-js"
-import { type ServerConnection, serverDisplayName } from "@/context/server"
+import { type ServerConnection, serverName } from "@/context/server"
import type { ServerHealth } from "@/utils/server-health"
interface ServerRowProps extends ParentProps {
@@ -20,13 +21,14 @@ interface ServerRowProps extends ParentProps {
versionClass?: string
dimmed?: boolean
badge?: JSXElement
+ showCredentials?: boolean
}
export function ServerRow(props: ServerRowProps) {
const [truncated, setTruncated] = createSignal(false)
let nameRef: HTMLSpanElement | undefined
let versionRef: HTMLSpanElement | undefined
- const name = createMemo(() => serverDisplayName(props.conn))
+ const name = createMemo(() => serverName(props.conn))
const check = () => {
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
@@ -52,35 +54,71 @@ export function ServerRow(props: ServerRowProps) {
const tooltipValue = () => (
- {name()}
+ {serverName(props.conn, true)}
- {props.status?.version}
+ v{props.status?.version}
)
+ const badge = children(() => props.badge)
+
return (
-
+
-
-
- {name()}
-
-
-
- {props.status?.version}
-
-
- {props.badge}
+
+
+
+ {name()}
+
+
+
+ v{props.status?.version}
+
+
+ }
+ >
+ {(badge) => badge()}
+
+
+
+ {(conn) => (
+
+
+ {conn().http.username ? (
+ {conn().http.username}
+ ) : (
+ no username
+ )}
+
+ {conn().http.password && •••••••• }
+
+ )}
+
+
{props.children}
)
}
+
+export function ServerHealthIndicator(props: { health?: ServerHealth }) {
+ return (
+
+ )
+}
diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx
index 47030aa177..08ae4d3194 100644
--- a/packages/app/src/components/session-context-usage.tsx
+++ b/packages/app/src/components/session-context-usage.tsx
@@ -39,7 +39,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const usd = createMemo(
() =>
- new Intl.NumberFormat(language.locale(), {
+ new Intl.NumberFormat(language.intl(), {
style: "currency",
currency: "USD",
}),
@@ -77,7 +77,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
{(ctx) => (
<>
- {ctx().total.toLocaleString(language.locale())}
+ {ctx().total.toLocaleString(language.intl())}
{language.t("context.usage.tokens")}
diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx
index 1ea97c395c..39eb4b4c0e 100644
--- a/packages/app/src/components/session/session-context-tab.tsx
+++ b/packages/app/src/components/session/session-context-tab.tsx
@@ -9,7 +9,7 @@ import { same } from "@/utils/same"
import { Icon } from "@opencode-ai/ui/icon"
import { Accordion } from "@opencode-ai/ui/accordion"
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
-import { Code } from "@opencode-ai/ui/code"
+import { File } from "@opencode-ai/ui/file"
import { Markdown } from "@opencode-ai/ui/markdown"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
@@ -47,7 +47,8 @@ function RawMessageContent(props: { message: Message; getParts: (id: string) =>
})
return (
-
- new Intl.NumberFormat(language.locale(), {
+ new Intl.NumberFormat(language.intl(), {
style: "currency",
currency: "USD",
}),
@@ -135,7 +136,7 @@ export function SessionContextTab() {
const metrics = createMemo(() => getSessionContextMetrics(messages(), sync.data.provider.all))
const ctx = createMemo(() => metrics().context)
- const formatter = createMemo(() => createSessionContextFormatter(language.locale()))
+ const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
const cost = createMemo(() => {
return usd().format(metrics().totalCost)
@@ -199,7 +200,7 @@ export function SessionContextTab() {
const stats = [
{ label: "context.stats.session", value: () => info()?.title ?? params.id ?? "—" },
- { label: "context.stats.messages", value: () => counts().all.toLocaleString(language.locale()) },
+ { label: "context.stats.messages", value: () => counts().all.toLocaleString(language.intl()) },
{ label: "context.stats.provider", value: providerLabel },
{ label: "context.stats.model", value: modelLabel },
{ label: "context.stats.limit", value: () => formatter().number(ctx()?.limit) },
@@ -212,8 +213,8 @@ export function SessionContextTab() {
label: "context.stats.cacheTokens",
value: () => `${formatter().number(ctx()?.cacheRead)} / ${formatter().number(ctx()?.cacheWrite)}`,
},
- { label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.locale()) },
- { label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.locale()) },
+ { label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
+ { label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
{ label: "context.stats.totalCost", value: cost },
{ label: "context.stats.sessionCreated", value: () => formatter().time(info()?.time.created) },
{ label: "context.stats.lastActivity", value: () => formatter().time(ctx()?.message.time.created) },
@@ -306,7 +307,7 @@ export function SessionContextTab() {
{breakdownLabel(segment.key)}
-
{segment.percent.toLocaleString(language.locale())}%
+
{segment.percent.toLocaleString(language.intl())}%
)}
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index ae8fc200f2..9b4551584c 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -1,28 +1,28 @@
+import { AppIcon } from "@opencode-ai/ui/app-icon"
+import { Button } from "@opencode-ai/ui/button"
+import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
+import { Icon } from "@opencode-ai/ui/icon"
+import { IconButton } from "@opencode-ai/ui/icon-button"
+import { Keybind } from "@opencode-ai/ui/keybind"
+import { Popover } from "@opencode-ai/ui/popover"
+import { Spinner } from "@opencode-ai/ui/spinner"
+import { TextField } from "@opencode-ai/ui/text-field"
+import { showToast } from "@opencode-ai/ui/toast"
+import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { getFilename } from "@opencode-ai/util/path"
+import { useParams } from "@solidjs/router"
import { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
-import { useParams } from "@solidjs/router"
-import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
+import { useGlobalSDK } from "@/context/global-sdk"
import { useLanguage } from "@/context/language"
+import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform"
import { useServer } from "@/context/server"
import { useSync } from "@/context/sync"
-import { useGlobalSDK } from "@/context/global-sdk"
-import { getFilename } from "@opencode-ai/util/path"
import { decode64 } from "@/utils/base64"
import { Persist, persisted } from "@/utils/persist"
-
-import { Icon } from "@opencode-ai/ui/icon"
-import { IconButton } from "@opencode-ai/ui/icon-button"
-import { Button } from "@opencode-ai/ui/button"
-import { AppIcon } from "@opencode-ai/ui/app-icon"
-import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
-import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
-import { Popover } from "@opencode-ai/ui/popover"
-import { TextField } from "@opencode-ai/ui/text-field"
-import { Keybind } from "@opencode-ai/ui/keybind"
-import { showToast } from "@opencode-ai/ui/toast"
import { StatusPopover } from "../status-popover"
const OPEN_APPS = [
@@ -35,6 +35,7 @@ const OPEN_APPS = [
"terminal",
"iterm2",
"ghostty",
+ "warp",
"xcode",
"android-studio",
"powershell",
@@ -45,32 +46,68 @@ type OpenApp = (typeof OPEN_APPS)[number]
type OS = "macos" | "windows" | "linux" | "unknown"
const MAC_APPS = [
- { id: "vscode", label: "VS Code", icon: "vscode", openWith: "Visual Studio Code" },
+ {
+ id: "vscode",
+ label: "VS Code",
+ icon: "vscode",
+ openWith: "Visual Studio Code",
+ },
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
{ id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
- { id: "antigravity", label: "Antigravity", icon: "antigravity", openWith: "Antigravity" },
+ {
+ id: "antigravity",
+ label: "Antigravity",
+ icon: "antigravity",
+ openWith: "Antigravity",
+ },
{ id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
+ { id: "warp", label: "Warp", icon: "warp", openWith: "Warp" },
{ id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
- { id: "android-studio", label: "Android Studio", icon: "android-studio", openWith: "Android Studio" },
- { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
+ {
+ id: "android-studio",
+ label: "Android Studio",
+ icon: "android-studio",
+ openWith: "Android Studio",
+ },
+ {
+ id: "sublime-text",
+ label: "Sublime Text",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
] as const
const WINDOWS_APPS = [
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
- { id: "powershell", label: "PowerShell", icon: "powershell", openWith: "powershell" },
- { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
+ {
+ id: "powershell",
+ label: "PowerShell",
+ icon: "powershell",
+ openWith: "powershell",
+ },
+ {
+ id: "sublime-text",
+ label: "Sublime Text",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
] as const
const LINUX_APPS = [
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
- { id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
+ {
+ id: "sublime-text",
+ label: "Sublime Text",
+ icon: "sublime-text",
+ openWith: "Sublime Text",
+ },
] as const
type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
@@ -101,12 +138,12 @@ function useSessionShare(args: {
globalSDK: ReturnType
currentSession: () =>
| {
- id: string
share?: {
url?: string
}
}
| undefined
+ sessionID: () => string | undefined
projectDirectory: () => string
platform: ReturnType
}) {
@@ -130,11 +167,11 @@ function useSessionShare(args: {
})
const shareSession = () => {
- const session = args.currentSession()
- if (!session || state.share) return
+ const sessionID = args.sessionID()
+ if (!sessionID || state.share) return
setState("share", true)
args.globalSDK.client.session
- .share({ sessionID: session.id, directory: args.projectDirectory() })
+ .share({ sessionID, directory: args.projectDirectory() })
.catch((error) => {
console.error("Failed to share session", error)
})
@@ -144,11 +181,11 @@ function useSessionShare(args: {
}
const unshareSession = () => {
- const session = args.currentSession()
- if (!session || state.unshare) return
+ const sessionID = args.sessionID()
+ if (!sessionID || state.unshare) return
setState("unshare", true)
args.globalSDK.client.session
- .unshare({ sessionID: session.id, directory: args.projectDirectory() })
+ .unshare({ sessionID, directory: args.projectDirectory() })
.catch((error) => {
console.error("Failed to unshare session", error)
})
@@ -206,14 +243,16 @@ export function SessionHeader() {
})
const hotkey = createMemo(() => command.keybind("file.open"))
- const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
+ const currentSession = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
- const showShare = createMemo(() => shareEnabled() && !!currentSession())
+ const showShare = createMemo(() => shareEnabled() && !!params.id)
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const view = createMemo(() => layout.view(sessionKey))
const os = createMemo(() => detectOS(platform))
- const [exists, setExists] = createStore>>({ finder: true })
+ const [exists, setExists] = createStore>>({
+ finder: true,
+ })
const apps = createMemo(() => {
if (os() === "macos") return MAC_APPS
@@ -259,18 +298,38 @@ export function SessionHeader() {
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
const [menu, setMenu] = createStore({ open: false })
+ const [openRequest, setOpenRequest] = createStore({
+ app: undefined as OpenApp | undefined,
+ })
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
- const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
+ const current = createMemo(
+ () =>
+ options().find((o) => o.id === prefs.app) ??
+ options()[0] ??
+ ({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
+ )
+ const opening = createMemo(() => openRequest.app !== undefined)
+
+ const selectApp = (app: OpenApp) => {
+ if (!options().some((item) => item.id === app)) return
+ setPrefs("app", app)
+ }
const openDir = (app: OpenApp) => {
+ if (opening() || !canOpen() || !platform.openPath) return
const directory = projectDirectory()
if (!directory) return
- if (!canOpen()) return
const item = options().find((o) => o.id === app)
const openWith = item && "openWith" in item ? item.openWith : undefined
- Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => showRequestError(language, err))
+ setOpenRequest("app", app)
+ platform
+ .openPath(directory, openWith)
+ .catch((err: unknown) => showRequestError(language, err))
+ .finally(() => {
+ setOpenRequest("app", undefined)
+ })
}
const copyPath = () => {
@@ -292,6 +351,7 @@ export function SessionHeader() {
const share = useSessionShare({
globalSDK,
currentSession,
+ sessionID: () => params.id,
projectDirectory,
platform,
})
@@ -315,7 +375,9 @@ export function SessionHeader() {
- {language.t("session.header.search.placeholder", { project: name() })}
+ {language.t("session.header.search.placeholder", {
+ project: name(),
+ })}
@@ -357,14 +419,23 @@ export function SessionHeader() {
openDir(current().id)}
+ disabled={opening()}
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
>
- Open
+ {language.t("common.open")}
@@ -388,13 +463,14 @@ export function SessionHeader() {
value={current().id}
onChange={(value) => {
if (!OPEN_APPS.includes(value as OpenApp)) return
- setPrefs("app", value as OpenApp)
+ selectApp(value as OpenApp)
}}
>
{(o) => (
{
setMenu("open", false)
openDir(o.id)
@@ -452,7 +528,10 @@ export function SessionHeader() {
variant: "ghost",
class:
"rounded-md h-[24px] px-3 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active",
- classList: { "rounded-r-none": share.shareUrl() !== undefined },
+ classList: {
+ "rounded-r-none": share.shareUrl() !== undefined,
+ "border-r-0": share.shareUrl() !== undefined,
+ },
style: { scale: 1 },
}}
trigger={{language.t("session.share.action.share")} }
diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx
index b7a544ba9a..52251dbb20 100644
--- a/packages/app/src/components/session/session-new-view.tsx
+++ b/packages/app/src/components/session/session-new-view.tsx
@@ -4,12 +4,12 @@ import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
+import { Mark } from "@opencode-ai/ui/logo"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
const MAIN_WORKTREE = "main"
const CREATE_WORKTREE = "create"
-const ROOT_CLASS =
- "size-full flex flex-col justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto 2xl:max-w-[1000px] px-6 pb-16"
+const ROOT_CLASS = "size-full flex flex-col"
interface NewSessionViewProps {
worktree: string
@@ -50,33 +50,43 @@ export function NewSessionView(props: NewSessionViewProps) {
return (
-
{language.t("command.session.new")}
-
-
-
- {getDirectory(projectRoot())}
-
{getFilename(projectRoot())}
+
+
+
+
+
+
{language.t("session.new.title")}
+
+
+
+
+ {getDirectory(projectRoot())}
+ {getFilename(projectRoot())}
+
+
+
+
+
+ {label(current())}
+
+
+
+ {(project) => (
+
+
+ {language.t("session.new.lastModified")}
+
+ {DateTime.fromMillis(project().time.updated ?? project().time.created)
+ .setLocale(language.intl())
+ .toRelative()}
+
+
+
+ )}
+
+
-
-
- {(project) => (
-
-
-
- {language.t("session.new.lastModified")}
-
- {DateTime.fromMillis(project().time.updated ?? project().time.created)
- .setLocale(language.locale())
- .toRelative()}
-
-
-
- )}
-
)
}
diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx
index b94e7a8e96..dfda91c160 100644
--- a/packages/app/src/components/session/session-sortable-tab.tsx
+++ b/packages/app/src/components/session/session-sortable-tab.tsx
@@ -13,13 +13,15 @@ import { useCommand } from "@/context/command"
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
return (
-
+ }
+ >
+
+
+
+
+
{getFilename(props.path)}
)
@@ -37,8 +39,8 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
return
})
return (
-
-
+
+
v
title={language.t("common.closeTab")}
keybind={command.keybind("tab.close")}
placement="bottom"
+ gutter={10}
>
{
+const stopDemoSound = () => {
if (demoSoundState.cleanup) {
demoSoundState.cleanup()
}
-
clearTimeout(demoSoundState.timeout)
+ demoSoundState.cleanup = undefined
+}
+
+const playDemoSound = (src: string | undefined) => {
+ stopDemoSound()
+ if (!src) return
demoSoundState.timeout = setTimeout(() => {
demoSoundState.cleanup = playSound(src)
@@ -132,11 +137,17 @@ export const SettingsGeneral: Component = () => {
] as const
const fontOptionsList = [...fontOptions]
- const soundOptions = [...SOUND_OPTIONS]
+ const noneSound = { id: "none", label: "sound.option.none", src: undefined } as const
+ const soundOptions = [noneSound, ...SOUND_OPTIONS]
- const soundSelectProps = (current: () => string, set: (id: string) => void) => ({
+ const soundSelectProps = (
+ enabled: () => boolean,
+ current: () => string,
+ setEnabled: (value: boolean) => void,
+ set: (id: string) => void,
+ ) => ({
options: soundOptions,
- current: soundOptions.find((o) => o.id === current()),
+ current: enabled() ? (soundOptions.find((o) => o.id === current()) ?? noneSound) : noneSound,
value: (o: (typeof soundOptions)[number]) => o.id,
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
@@ -145,6 +156,12 @@ export const SettingsGeneral: Component = () => {
},
onSelect: (option: (typeof soundOptions)[number] | undefined) => {
if (!option) return
+ if (option.id === "none") {
+ setEnabled(false)
+ stopDemoSound()
+ return
+ }
+ setEnabled(true)
set(option.id)
playDemoSound(option.src)
},
@@ -250,18 +267,50 @@ export const SettingsGeneral: Component = () => {
)}
+
+
+ )
+ const FeedSection = () => (
+
+
{language.t("settings.general.section.feed")}
+
+
-
+
settings.general.setShowReasoningSummaries(checked)}
/>
+
+
+
+ settings.general.setShellToolPartsExpanded(checked)}
+ />
+
+
+
+
+
+ settings.general.setEditToolPartsExpanded(checked)}
+ />
+
+
)
@@ -319,66 +368,45 @@ export const SettingsGeneral: Component = () => {
title={language.t("settings.general.sounds.agent.title")}
description={language.t("settings.general.sounds.agent.description")}
>
-
-
- settings.sounds.setAgentEnabled(checked)}
- />
-
-
settings.sounds.agent(),
- (id) => settings.sounds.setAgent(id),
- )}
- />
-
+
settings.sounds.agentEnabled(),
+ () => settings.sounds.agent(),
+ (value) => settings.sounds.setAgentEnabled(value),
+ (id) => settings.sounds.setAgent(id),
+ )}
+ />
-
-
- settings.sounds.setPermissionsEnabled(checked)}
- />
-
-
settings.sounds.permissions(),
- (id) => settings.sounds.setPermissions(id),
- )}
- />
-
+ settings.sounds.permissionsEnabled(),
+ () => settings.sounds.permissions(),
+ (value) => settings.sounds.setPermissionsEnabled(value),
+ (id) => settings.sounds.setPermissions(id),
+ )}
+ />
-
-
- settings.sounds.setErrorsEnabled(checked)}
- />
-
-
settings.sounds.errors(),
- (id) => settings.sounds.setErrors(id),
- )}
- />
-
+ settings.sounds.errorsEnabled(),
+ () => settings.sounds.errors(),
+ (value) => settings.sounds.setErrorsEnabled(value),
+ (id) => settings.sounds.setErrors(id),
+ )}
+ />
@@ -439,6 +467,8 @@ export const SettingsGeneral: Component = () => {
+
+
diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx
index 07f6d30e18..eff62cd6c9 100644
--- a/packages/app/src/components/settings-models.tsx
+++ b/packages/app/src/components/settings-models.tsx
@@ -4,7 +4,6 @@ import { Switch } from "@opencode-ai/ui/switch"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TextField } from "@opencode-ai/ui/text-field"
-import type { IconName } from "@opencode-ai/ui/icons/provider"
import { type Component, For, Show } from "solid-js"
import { useLanguage } from "@/context/language"
import { useModels } from "@/context/models"
@@ -98,7 +97,7 @@ export const SettingsModels: Component = () => {
{(group) => (
-
+
{group.items[0].provider.name}
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx
index d1837ee607..a9839758b7 100644
--- a/packages/app/src/components/settings-providers.tsx
+++ b/packages/app/src/components/settings-providers.tsx
@@ -3,7 +3,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Tag } from "@opencode-ai/ui/tag"
import { showToast } from "@opencode-ai/ui/toast"
-import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { createMemo, type Component, For, Show } from "solid-js"
import { useLanguage } from "@/context/language"
@@ -18,6 +17,7 @@ type ProviderItem = ReturnType
["connected"]>[num
const PROVIDER_NOTES = [
{ match: (id: string) => id === "opencode", key: "dialog.provider.opencode.note" },
+ { match: (id: string) => id === "opencode-go", key: "dialog.provider.opencodeGo.tagline" },
{ match: (id: string) => id === "anthropic", key: "dialog.provider.anthropic.note" },
{ match: (id: string) => id.startsWith("github-copilot"), key: "dialog.provider.copilot.note" },
{ match: (id: string) => id === "openai", key: "dialog.provider.openai.note" },
@@ -33,11 +33,6 @@ export const SettingsProviders: Component = () => {
const globalSync = useGlobalSync()
const providers = useProviders()
- const icon = (id: string): IconName => {
- if (iconNames.includes(id as IconName)) return id as IconName
- return "synthetic"
- }
-
const connected = createMemo(() => {
return providers
.connected()
@@ -154,7 +149,7 @@ export const SettingsProviders: Component = () => {
{(item) => (
-
+
{item.name}
{type(item)}
@@ -162,7 +157,7 @@ export const SettingsProviders: Component = () => {
when={canDisconnect(item)}
fallback={
- Connected from your environment variables
+ {language.t("settings.providers.connected.environmentDescription")}
}
>
@@ -185,11 +180,14 @@ export const SettingsProviders: Component = () => {
-
+
{item.name}
{language.t("dialog.provider.tag.recommended")}
+
+ {language.t("dialog.provider.tag.recommended")}
+
{(key) => {language.t(key())} }
@@ -215,11 +213,13 @@ export const SettingsProviders: Component = () => {
>
-
-
Custom provider
+
+
{language.t("provider.custom.title")}
{language.t("settings.providers.tag.custom")}
-
Add an OpenAI-compatible provider by base URL.
+
+ {language.t("settings.providers.custom.description")}
+
-
- {language.t("status.popover.trigger")}
+
}
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
gutter={4}
placement="bottom-end"
- shift={-136}
+ shift={-168}
>
+
{
pty: LocalPTY
onSubmit?: () => void
- onCleanup?: (pty: LocalPTY) => void
+ onCleanup?: (pty: Partial & { id: string }) => void
onConnect?: () => void
onConnectError?: (error: unknown) => void
}
@@ -126,8 +126,8 @@ const persistTerminal = (input: {
term: Term | undefined
addon: SerializeAddon | undefined
cursor: number
- pty: LocalPTY
- onCleanup?: (pty: LocalPTY) => void
+ id: string
+ onCleanup?: (pty: Partial & { id: string }) => void
}) => {
if (!input.addon || !input.onCleanup || !input.term) return
const buffer = (() => {
@@ -140,7 +140,7 @@ const persistTerminal = (input: {
})()
input.onCleanup({
- ...input.pty,
+ id: input.id,
buffer,
cursor: input.cursor,
rows: input.term.rows,
@@ -158,6 +158,19 @@ export const Terminal = (props: TerminalProps) => {
const server = useServer()
let container!: HTMLDivElement
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"])
+ const id = local.pty.id
+ const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
+ const restoreSize =
+ restore &&
+ typeof local.pty.cols === "number" &&
+ Number.isSafeInteger(local.pty.cols) &&
+ local.pty.cols > 0 &&
+ typeof local.pty.rows === "number" &&
+ Number.isSafeInteger(local.pty.rows) &&
+ local.pty.rows > 0
+ ? { cols: local.pty.cols, rows: local.pty.rows }
+ : undefined
+ const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined
let ws: WebSocket | undefined
let term: Term | undefined
let ghostty: Ghostty
@@ -190,7 +203,7 @@ export const Terminal = (props: TerminalProps) => {
const pushSize = (cols: number, rows: number) => {
return sdk.client.pty
.update({
- ptyID: local.pty.id,
+ ptyID: id,
size: { cols, rows },
})
.catch((err) => {
@@ -204,7 +217,7 @@ export const Terminal = (props: TerminalProps) => {
const currentTheme = theme.themes()[theme.themeId()]
if (!currentTheme) return fallback
const variant = mode === "dark" ? currentTheme.dark : currentTheme.light
- if (!variant?.seeds) return fallback
+ if (!variant?.seeds && !variant?.palette) return fallback
const resolved = resolveThemeVariant(variant, mode === "dark")
const text = resolved["text-stronger"] ?? fallback.foreground
const background = resolved["background-stronger"] ?? fallback.background
@@ -219,7 +232,7 @@ export const Terminal = (props: TerminalProps) => {
}
}
- const [terminalColors, setTerminalColors] = createSignal(getTerminalColors())
+ const terminalColors = createMemo(getTerminalColors)
const scheduleFit = () => {
if (disposed) return
@@ -259,8 +272,7 @@ export const Terminal = (props: TerminalProps) => {
}
createEffect(() => {
- const colors = getTerminalColors()
- setTerminalColors(colors)
+ const colors = terminalColors()
if (!term) return
setOptionIfSupported(term, "theme", colors)
})
@@ -320,18 +332,6 @@ export const Terminal = (props: TerminalProps) => {
const mod = loaded.mod
const g = loaded.ghostty
- const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
- const restoreSize =
- restore &&
- typeof local.pty.cols === "number" &&
- Number.isSafeInteger(local.pty.cols) &&
- local.pty.cols > 0 &&
- typeof local.pty.rows === "number" &&
- Number.isSafeInteger(local.pty.rows) &&
- local.pty.rows > 0
- ? { cols: local.pty.cols, rows: local.pty.rows }
- : undefined
-
const t = new mod.Terminal({
cursorBlink: true,
cursorStyle: "bar",
@@ -428,14 +428,14 @@ export const Terminal = (props: TerminalProps) => {
await write(restore)
fit.fit()
scheduleSize(t.cols, t.rows)
- if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ if (scrollY !== undefined) t.scrollToLine(scrollY)
startResize()
} else {
fit.fit()
scheduleSize(t.cols, t.rows)
if (restore) {
await write(restore)
- if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ if (scrollY !== undefined) t.scrollToLine(scrollY)
}
startResize()
}
@@ -447,9 +447,9 @@ export const Terminal = (props: TerminalProps) => {
const once = { value: false }
let closing = false
- const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
+ const url = new URL(sdk.url + `/pty/${id}/connect`)
url.searchParams.set("directory", sdk.directory)
- url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
+ url.searchParams.set("cursor", String(start !== undefined ? start : restore ? -1 : 0))
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
url.username = server.current?.http.username ?? ""
url.password = server.current?.http.password ?? ""
@@ -543,7 +543,7 @@ export const Terminal = (props: TerminalProps) => {
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close(1000)
const finalize = () => {
- persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
+ persistTerminal({ term, addon: serializeAddon, cursor, id, onCleanup: props.onCleanup })
cleanup()
}
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx
index 760f40fc0e..b45f811501 100644
--- a/packages/app/src/components/titlebar.tsx
+++ b/packages/app/src/components/titlebar.tsx
@@ -155,8 +155,9 @@ export function Titlebar() {
return (
@@ -268,7 +269,7 @@ export function Titlebar() {
diff --git a/packages/app/src/context/comments.test.ts b/packages/app/src/context/comments.test.ts
index bee5c7871e..82fa170f2f 100644
--- a/packages/app/src/context/comments.test.ts
+++ b/packages/app/src/context/comments.test.ts
@@ -150,4 +150,37 @@ describe("comments session indexing", () => {
dispose()
})
})
+
+ test("update changes only the targeted comment body", () => {
+ createRoot((dispose) => {
+ const comments = createCommentSessionForTest({
+ "a.ts": [line("a.ts", "a1", 10), line("a.ts", "a2", 20)],
+ })
+
+ comments.update("a.ts", "a2", "edited")
+
+ expect(comments.list("a.ts").map((item) => item.comment)).toEqual(["a1", "edited"])
+
+ dispose()
+ })
+ })
+
+ test("replace swaps comment state and clears focus state", () => {
+ createRoot((dispose) => {
+ const comments = createCommentSessionForTest({
+ "a.ts": [line("a.ts", "a1", 10)],
+ })
+
+ comments.setFocus({ file: "a.ts", id: "a1" })
+ comments.setActive({ file: "a.ts", id: "a1" })
+ comments.replace([line("b.ts", "b1", 30)])
+
+ expect(comments.list("a.ts")).toEqual([])
+ expect(comments.list("b.ts").map((item) => item.id)).toEqual(["b1"])
+ expect(comments.focus()).toBeNull()
+ expect(comments.active()).toBeNull()
+
+ dispose()
+ })
+ })
})
diff --git a/packages/app/src/context/comments.tsx b/packages/app/src/context/comments.tsx
index ecf63e45b6..a97010c0af 100644
--- a/packages/app/src/context/comments.tsx
+++ b/packages/app/src/context/comments.tsx
@@ -44,6 +44,37 @@ function aggregate(comments: Record
) {
.sort((a, b) => a.time - b.time)
}
+function cloneSelection(selection: SelectedLineRange): SelectedLineRange {
+ const next: SelectedLineRange = {
+ start: selection.start,
+ end: selection.end,
+ }
+
+ if (selection.side) next.side = selection.side
+ if (selection.endSide) next.endSide = selection.endSide
+ return next
+}
+
+function cloneComment(comment: LineComment): LineComment {
+ return {
+ ...comment,
+ selection: cloneSelection(comment.selection),
+ }
+}
+
+function group(comments: LineComment[]) {
+ return comments.reduce>((acc, comment) => {
+ const list = acc[comment.file]
+ const next = cloneComment(comment)
+ if (list) {
+ list.push(next)
+ return acc
+ }
+ acc[comment.file] = [next]
+ return acc
+ }, {})
+}
+
function createCommentSessionState(store: Store, setStore: SetStoreFunction) {
const [state, setState] = createStore({
focus: null as CommentFocus | null,
@@ -70,6 +101,7 @@ function createCommentSessionState(store: Store, setStore: SetStor
id: uuid(),
time: Date.now(),
...input,
+ selection: cloneSelection(input.selection),
}
batch(() => {
@@ -87,6 +119,23 @@ function createCommentSessionState(store: Store, setStore: SetStor
})
}
+ const update = (file: string, id: string, comment: string) => {
+ setStore("comments", file, (items) =>
+ (items ?? []).map((item) => {
+ if (item.id !== id) return item
+ return { ...item, comment }
+ }),
+ )
+ }
+
+ const replace = (comments: LineComment[]) => {
+ batch(() => {
+ setStore("comments", reconcile(group(comments)))
+ setFocus(null)
+ setActive(null)
+ })
+ }
+
const clear = () => {
batch(() => {
setStore("comments", reconcile({}))
@@ -100,6 +149,8 @@ function createCommentSessionState(store: Store, setStore: SetStor
all,
add,
remove,
+ update,
+ replace,
clear,
focus: () => state.focus,
setFocus,
@@ -132,6 +183,8 @@ function createCommentSession(dir: string, id: string | undefined) {
all: session.all,
add: session.add,
remove: session.remove,
+ update: session.update,
+ replace: session.replace,
clear: session.clear,
focus: session.focus,
setFocus: session.setFocus,
@@ -176,6 +229,8 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
all: () => session().all(),
add: (input: Omit) => session().add(input),
remove: (file: string, id: string) => session().remove(file, id),
+ update: (file: string, id: string, comment: string) => session().update(file, id, comment),
+ replace: (comments: LineComment[]) => session().replace(comments),
clear: () => session().clear(),
focus: () => session().focus(),
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
diff --git a/packages/app/src/context/file/path.test.ts b/packages/app/src/context/file/path.test.ts
index f2a3c44b6c..feef6d466e 100644
--- a/packages/app/src/context/file/path.test.ts
+++ b/packages/app/src/context/file/path.test.ts
@@ -13,6 +13,14 @@ describe("file path helpers", () => {
expect(path.pathFromTab("other://src/app.ts")).toBeUndefined()
})
+ test("normalizes Windows absolute paths with mixed separators", () => {
+ const path = createPathHelpers(() => "C:\\repo")
+ expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src\\app.ts")
+ expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts")
+ expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts")
+ expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src\\app.ts")
+ })
+
test("keeps query/hash stripping behavior stable", () => {
expect(stripQueryAndHash("a/b.ts#L12?x=1")).toBe("a/b.ts")
expect(stripQueryAndHash("a/b.ts?x=1#L12")).toBe("a/b.ts")
diff --git a/packages/app/src/context/file/path.ts b/packages/app/src/context/file/path.ts
index 859fdc0406..53f072b6cb 100644
--- a/packages/app/src/context/file/path.ts
+++ b/packages/app/src/context/file/path.ts
@@ -104,26 +104,29 @@ export function encodeFilePath(filepath: string): string {
export function createPathHelpers(scope: () => string) {
const normalize = (input: string) => {
const root = scope()
- const prefix = root.endsWith("/") ? root : root + "/"
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
- if (path.startsWith(prefix)) {
- path = path.slice(prefix.length)
- }
-
- if (path.startsWith(root)) {
+ // Separator-agnostic prefix stripping for Cygwin/native Windows compatibility
+ // Only case-insensitive on Windows (drive letter or UNC paths)
+ const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\")
+ const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/")
+ const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/")
+ if (
+ canonPath.startsWith(canonRoot) &&
+ (canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/")
+ ) {
+ // Slice from original path to preserve native separators
path = path.slice(root.length)
}
- if (path.startsWith("./")) {
+ if (path.startsWith("./") || path.startsWith(".\\")) {
path = path.slice(2)
}
- if (path.startsWith("/")) {
+ if (path.startsWith("/") || path.startsWith("\\")) {
path = path.slice(1)
}
-
return path
}
diff --git a/packages/app/src/context/file/view-cache.ts b/packages/app/src/context/file/view-cache.ts
index 6e8ddf62df..4c060174ab 100644
--- a/packages/app/src/context/file/view-cache.ts
+++ b/packages/app/src/context/file/view-cache.ts
@@ -9,7 +9,7 @@ const MAX_FILE_VIEW_SESSIONS = 20
const MAX_VIEW_FILES = 500
function normalizeSelectedLines(range: SelectedLineRange): SelectedLineRange {
- if (range.start <= range.end) return range
+ if (range.start <= range.end) return { ...range }
const startSide = range.side
const endSide = range.endSide ?? startSide
diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx
index 8c0035d555..c1a87b95b8 100644
--- a/packages/app/src/context/global-sdk.tsx
+++ b/packages/app/src/context/global-sdk.tsx
@@ -49,9 +49,12 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
let queue: Queued[] = []
let buffer: Queued[] = []
const coalesced = new Map()
+ const staleDeltas = new Set()
let timer: ReturnType | undefined
let last = 0
+ const deltaKey = (directory: string, messageID: string, partID: string) => `${directory}:${messageID}:${partID}`
+
const key = (directory: string, payload: Event) => {
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`
if (payload.type === "lsp.updated") return `lsp.updated:${directory}`
@@ -68,14 +71,20 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
if (queue.length === 0) return
const events = queue
+ const skip = staleDeltas.size > 0 ? new Set(staleDeltas) : undefined
queue = buffer
buffer = events
queue.length = 0
coalesced.clear()
+ staleDeltas.clear()
last = Date.now()
batch(() => {
for (const event of events) {
+ if (skip && event.payload.type === "message.part.delta") {
+ const props = event.payload.properties
+ if (skip.has(deltaKey(event.directory, props.messageID, props.partID))) continue
+ }
emitter.emit(event.directory, event.payload)
}
})
@@ -144,6 +153,10 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
const i = coalesced.get(k)
if (i !== undefined) {
queue[i] = { directory, payload }
+ if (payload.type === "message.part.updated") {
+ const part = payload.properties.part
+ staleDeltas.add(deltaKey(directory, part.messageID, part.id))
+ }
continue
}
coalesced.set(k, queue.length)
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 7e242130f1..4090699a8b 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -11,7 +11,6 @@ import { showToast } from "@opencode-ai/ui/toast"
import { getFilename } from "@opencode-ai/util/path"
import {
createContext,
- createEffect,
getOwner,
Match,
onCleanup,
@@ -28,14 +27,14 @@ import type { InitError } from "../pages/error"
import { useGlobalSDK } from "./global-sdk"
import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap"
import { createChildStoreManager } from "./global-sync/child-store"
-import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
+import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./global-sync/event-reducer"
import { createRefreshQueue } from "./global-sync/queue"
import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global-sync/session-load"
import { trimSessions } from "./global-sync/session-trim"
import type { ProjectMeta } from "./global-sync/types"
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
import { sanitizeProject } from "./global-sync/utils"
-import { usePlatform } from "./platform"
+import { formatServerError } from "@/utils/server-errors"
type GlobalStore = {
ready: boolean
@@ -51,15 +50,8 @@ type GlobalStore = {
reload: undefined | "pending" | "complete"
}
-function errorMessage(error: unknown) {
- if (error instanceof Error && error.message) return error.message
- if (typeof error === "string" && error) return error
- return "Unknown error"
-}
-
function createGlobalSync() {
const globalSDK = useGlobalSDK()
- const platform = usePlatform()
const language = useLanguage()
const owner = getOwner()
if (!owner) throw new Error("GlobalSync must be created within owner")
@@ -69,7 +61,7 @@ function createGlobalSync() {
const sessionLoads = new Map>()
const sessionMeta = new Map()
- const [projectCache, setProjectCache, , projectCacheReady] = persisted(
+ const [projectCache, setProjectCache, projectInit] = persisted(
Persist.global("globalSync.project", ["globalSync.project.v1"]),
createStore({ value: [] as Project[] }),
)
@@ -85,6 +77,57 @@ function createGlobalSync() {
reload: undefined,
})
+ let active = true
+ let projectWritten = false
+
+ onCleanup(() => {
+ active = false
+ })
+
+ const cacheProjects = () => {
+ setProjectCache(
+ "value",
+ untrack(() => globalStore.project.map(sanitizeProject)),
+ )
+ }
+
+ const setProjects = (next: Project[] | ((draft: Project[]) => void)) => {
+ projectWritten = true
+ if (typeof next === "function") {
+ setGlobalStore("project", produce(next))
+ cacheProjects()
+ return
+ }
+ setGlobalStore("project", next)
+ cacheProjects()
+ }
+
+ const setBootStore = ((...input: unknown[]) => {
+ if (input[0] === "project" && Array.isArray(input[1])) {
+ setProjects(input[1] as Project[])
+ return input[1]
+ }
+ return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
+ }) as typeof setGlobalStore
+
+ const set = ((...input: unknown[]) => {
+ if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) {
+ setProjects(input[1] as Project[] | ((draft: Project[]) => void))
+ return input[1]
+ }
+ return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
+ }) as typeof setGlobalStore
+
+ if (projectInit instanceof Promise) {
+ void projectInit.then(() => {
+ if (!active) return
+ if (projectWritten) return
+ const cached = projectCache.value
+ if (cached.length === 0) return
+ setGlobalStore("project", cached)
+ })
+ }
+
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
if (!sessionID) return
if (!todos) {
@@ -132,30 +175,6 @@ function createGlobalSync() {
return sdk
}
- createEffect(() => {
- if (!projectCacheReady()) return
- if (globalStore.project.length !== 0) return
- const cached = projectCache.value
- if (cached.length === 0) return
- setGlobalStore("project", cached)
- })
-
- createEffect(() => {
- if (!projectCacheReady()) return
- const projects = globalStore.project
- if (projects.length === 0) {
- const cachedLength = untrack(() => projectCache.value.length)
- if (cachedLength !== 0) return
- }
- setProjectCache("value", projects.map(sanitizeProject))
- })
-
- createEffect(() => {
- if (globalStore.reload !== "complete") return
- setGlobalStore("reload", undefined)
- queue.refresh()
- })
-
async function loadSessions(directory: string) {
const pending = sessionLoads.get(directory)
if (pending) return pending
@@ -170,6 +189,7 @@ function createGlobalSync() {
})
if (next.length !== store.session.length) {
setStore("session", reconcile(next, { key: "id" }))
+ cleanupDroppedSessionCaches(store, setStore, next, setSessionTodo)
}
children.unpin(directory)
return
@@ -201,14 +221,16 @@ function createGlobalSync() {
}),
)
setStore("session", reconcile(sessions, { key: "id" }))
+ cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
sessionMeta.set(directory, { limit })
})
.catch((err) => {
console.error("Failed to load sessions", err)
const project = getFilename(directory)
showToast({
+ variant: "error",
title: language.t("toast.session.listFailed.title", { project }),
- description: errorMessage(err),
+ description: formatServerError(err, language.t),
})
})
@@ -238,6 +260,7 @@ function createGlobalSync() {
setStore: child[1],
vcsCache: cache,
loadSessions,
+ translate: language.t,
})
})()
@@ -258,13 +281,7 @@ function createGlobalSync() {
event,
project: globalStore.project,
refresh: queue.refresh,
- setGlobalProject(next) {
- if (typeof next === "function") {
- setGlobalStore("project", produce(next))
- return
- }
- setGlobalStore("project", next)
- },
+ setGlobalProject: setProjects,
})
if (event.type === "server.connected" || event.type === "global.disposed") {
for (const directory of Object.keys(children.children)) {
@@ -312,7 +329,9 @@ function createGlobalSync() {
url: globalSDK.url,
}),
requestFailedTitle: language.t("common.requestFailed"),
- setGlobalStore,
+ translate: language.t,
+ formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
+ setGlobalStore: setBootStore,
})
}
@@ -336,7 +355,9 @@ function createGlobalSync() {
.update({ config })
.then(bootstrap)
.then(() => {
- setGlobalStore("reload", "complete")
+ queue.refresh()
+ setGlobalStore("reload", undefined)
+ queue.refresh()
})
.catch((error) => {
setGlobalStore("reload", undefined)
@@ -346,7 +367,7 @@ function createGlobalSync() {
return {
data: globalStore,
- set: setGlobalStore,
+ set,
get ready() {
return globalStore.ready
},
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts
index 6e77148289..8b1a3c48c5 100644
--- a/packages/app/src/context/global-sync/bootstrap.ts
+++ b/packages/app/src/context/global-sync/bootstrap.ts
@@ -16,6 +16,7 @@ import { batch } from "solid-js"
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { State, VcsCache } from "./types"
import { cmp, normalizeProviderList } from "./utils"
+import { formatServerError } from "@/utils/server-errors"
type GlobalStore = {
ready: boolean
@@ -35,6 +36,8 @@ export async function bootstrapGlobal(input: {
connectErrorTitle: string
connectErrorDescription: string
requestFailedTitle: string
+ translate: (key: string, vars?: Record) => string
+ formatMoreCount: (count: number) => string
setGlobalStore: SetStoreFunction
}) {
const health = await input.globalSDK.global
@@ -87,8 +90,8 @@ export async function bootstrapGlobal(input: {
const results = await Promise.allSettled(tasks)
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
if (errors.length) {
- const message = errors[0] instanceof Error ? errors[0].message : String(errors[0])
- const more = errors.length > 1 ? ` (+${errors.length - 1} more)` : ""
+ const message = formatServerError(errors[0], input.translate)
+ const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
showToast({
variant: "error",
title: input.requestFailedTitle,
@@ -115,6 +118,7 @@ export async function bootstrapDirectory(input: {
setStore: SetStoreFunction
vcsCache: VcsCache
loadSessions: (directory: string) => Promise | void
+ translate: (key: string, vars?: Record) => string
}) {
if (input.store.status !== "complete") input.setStore("status", "loading")
@@ -133,8 +137,11 @@ export async function bootstrapDirectory(input: {
} catch (err) {
console.error("Failed to bootstrap instance", err)
const project = getFilename(input.directory)
- const message = err instanceof Error ? err.message : String(err)
- showToast({ title: `Failed to reload ${project}`, description: message })
+ showToast({
+ variant: "error",
+ title: `Failed to reload ${project}`,
+ description: formatServerError(err, input.translate),
+ })
input.setStore("status", "partial")
return
}
diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts
index 2fe5b78303..e2ada244fb 100644
--- a/packages/app/src/context/global-sync/child-store.ts
+++ b/packages/app/src/context/global-sync/child-store.ts
@@ -1,4 +1,4 @@
-import { createRoot, createEffect, getOwner, onCleanup, runWithOwner, type Accessor, type Owner } from "solid-js"
+import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
import { Persist, persisted } from "@/utils/persist"
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
@@ -131,8 +131,7 @@ export function createChildStoreManager(input: {
)
if (!vcs) throw new Error("Failed to create persisted cache")
const vcsStore = vcs[0]
- const vcsReady = vcs[3]
- vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcsReady })
+ vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
const meta = runWithOwner(input.owner, () =>
persisted(
@@ -154,10 +153,12 @@ export function createChildStoreManager(input: {
const init = () =>
createRoot((dispose) => {
+ const initialMeta = meta[0].value
+ const initialIcon = icon[0].value
const child = createStore({
project: "",
- projectMeta: meta[0].value,
- icon: icon[0].value,
+ projectMeta: initialMeta,
+ icon: initialIcon,
provider: { all: [], connected: [], default: {} },
config: {},
path: { state: "", config: "", worktree: "", directory: "", home: "" },
@@ -181,16 +182,27 @@ export function createChildStoreManager(input: {
children[directory] = child
disposers.set(directory, dispose)
- createEffect(() => {
- if (!vcsReady()) return
+ const onPersistedInit = (init: Promise | string | null, run: () => void) => {
+ if (!(init instanceof Promise)) return
+ void init.then(() => {
+ if (children[directory] !== child) return
+ run()
+ })
+ }
+
+ onPersistedInit(vcs[2], () => {
const cached = vcsStore.value
if (!cached?.branch) return
child[1]("vcs", (value) => value ?? cached)
})
- createEffect(() => {
+
+ onPersistedInit(meta[2], () => {
+ if (child[0].projectMeta !== initialMeta) return
child[1]("projectMeta", meta[0].value)
})
- createEffect(() => {
+
+ onPersistedInit(icon[2], () => {
+ if (child[0].icon !== initialIcon) return
child[1]("icon", icon[0].value)
})
})
diff --git a/packages/app/src/context/global-sync/event-reducer.test.ts b/packages/app/src/context/global-sync/event-reducer.test.ts
index ab7f99cef3..cf2da135cb 100644
--- a/packages/app/src/context/global-sync/event-reducer.test.ts
+++ b/packages/app/src/context/global-sync/event-reducer.test.ts
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
import type { Message, Part, PermissionRequest, Project, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
import { createStore } from "solid-js/store"
import type { State } from "./types"
-import { applyDirectoryEvent, applyGlobalEvent } from "./event-reducer"
+import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./event-reducer"
const rootSession = (input: { id: string; parentID?: string; archived?: number }) =>
({
@@ -248,6 +248,62 @@ describe("applyDirectoryEvent", () => {
}
})
+ test("cleans caches for trimmed sessions on session.created", () => {
+ const dropped = rootSession({ id: "ses_b" })
+ const kept = rootSession({ id: "ses_a" })
+ const message = userMessage("msg_1", dropped.id)
+ const todos: string[] = []
+ const [store, setStore] = createStore(
+ baseState({
+ limit: 1,
+ session: [dropped],
+ message: { [dropped.id]: [message] },
+ part: { [message.id]: [textPart("prt_1", dropped.id, message.id)] },
+ session_diff: { [dropped.id]: [] },
+ todo: { [dropped.id]: [] },
+ permission: { [dropped.id]: [] },
+ question: { [dropped.id]: [] },
+ session_status: { [dropped.id]: { type: "busy" } },
+ }),
+ )
+
+ applyDirectoryEvent({
+ event: { type: "session.created", properties: { info: kept } },
+ store,
+ setStore,
+ push() {},
+ directory: "/tmp",
+ loadLsp() {},
+ setSessionTodo(sessionID, value) {
+ if (value !== undefined) return
+ todos.push(sessionID)
+ },
+ })
+
+ expect(store.session.map((x) => x.id)).toEqual([kept.id])
+ expect(store.message[dropped.id]).toBeUndefined()
+ expect(store.part[message.id]).toBeUndefined()
+ expect(store.session_diff[dropped.id]).toBeUndefined()
+ expect(store.todo[dropped.id]).toBeUndefined()
+ expect(store.permission[dropped.id]).toBeUndefined()
+ expect(store.question[dropped.id]).toBeUndefined()
+ expect(store.session_status[dropped.id]).toBeUndefined()
+ expect(todos).toEqual([dropped.id])
+ })
+
+ test("cleanupDroppedSessionCaches clears part-only orphan state", () => {
+ const [store, setStore] = createStore(
+ baseState({
+ session: [rootSession({ id: "ses_keep" })],
+ part: { msg_1: [textPart("prt_1", "ses_drop", "msg_1")] },
+ }),
+ )
+
+ cleanupDroppedSessionCaches(store, setStore, store.session)
+
+ expect(store.part.msg_1).toBeUndefined()
+ })
+
test("upserts and removes messages while clearing orphaned parts", () => {
const sessionID = "ses_1"
const [store, setStore] = createStore(
diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts
index 241dfb14d7..b8eda0573f 100644
--- a/packages/app/src/context/global-sync/event-reducer.ts
+++ b/packages/app/src/context/global-sync/event-reducer.ts
@@ -13,6 +13,7 @@ import type {
} from "@opencode-ai/sdk/v2/client"
import type { State, VcsCache } from "./types"
import { trimSessions } from "./session-trim"
+import { dropSessionCaches } from "./session-cache"
export function applyGlobalEvent(input: {
event: { type: string; properties?: unknown }
@@ -40,37 +41,44 @@ export function applyGlobalEvent(input: {
}
function cleanupSessionCaches(
- store: Store,
setStore: SetStoreFunction,
sessionID: string,
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void,
) {
if (!sessionID) return
- const hasAny =
- store.message[sessionID] !== undefined ||
- store.session_diff[sessionID] !== undefined ||
- store.todo[sessionID] !== undefined ||
- store.permission[sessionID] !== undefined ||
- store.question[sessionID] !== undefined ||
- store.session_status[sessionID] !== undefined
setSessionTodo?.(sessionID, undefined)
- if (!hasAny) return
setStore(
produce((draft) => {
- const messages = draft.message[sessionID]
- if (messages) {
- for (const message of messages) {
- const id = message?.id
- if (!id) continue
- delete draft.part[id]
- }
- }
- delete draft.message[sessionID]
- delete draft.session_diff[sessionID]
- delete draft.todo[sessionID]
- delete draft.permission[sessionID]
- delete draft.question[sessionID]
- delete draft.session_status[sessionID]
+ dropSessionCaches(draft, [sessionID])
+ }),
+ )
+}
+
+export function cleanupDroppedSessionCaches(
+ store: Store,
+ setStore: SetStoreFunction,
+ next: Session[],
+ setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void,
+) {
+ const keep = new Set(next.map((item) => item.id))
+ const stale = [
+ ...Object.keys(store.message),
+ ...Object.keys(store.session_diff),
+ ...Object.keys(store.todo),
+ ...Object.keys(store.permission),
+ ...Object.keys(store.question),
+ ...Object.keys(store.session_status),
+ ...Object.values(store.part)
+ .map((parts) => parts?.find((part) => !!part?.sessionID)?.sessionID)
+ .filter((sessionID): sessionID is string => !!sessionID),
+ ].filter((sessionID, index, list) => !keep.has(sessionID) && list.indexOf(sessionID) === index)
+ if (stale.length === 0) return
+ for (const sessionID of stale) {
+ setSessionTodo?.(sessionID, undefined)
+ }
+ setStore(
+ produce((draft) => {
+ dropSessionCaches(draft, stale)
}),
)
}
@@ -102,6 +110,7 @@ export function applyDirectoryEvent(input: {
next.splice(result.index, 0, info)
const trimmed = trimSessions(next, { limit: input.store.limit, permission: input.store.permission })
input.setStore("session", reconcile(trimmed, { key: "id" }))
+ cleanupDroppedSessionCaches(input.store, input.setStore, trimmed, input.setSessionTodo)
if (!info.parentID) input.setStore("sessionTotal", (value) => value + 1)
break
}
@@ -117,7 +126,7 @@ export function applyDirectoryEvent(input: {
}),
)
}
- cleanupSessionCaches(input.store, input.setStore, info.id, input.setSessionTodo)
+ cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo)
if (info.parentID) break
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
break
@@ -130,6 +139,7 @@ export function applyDirectoryEvent(input: {
next.splice(result.index, 0, info)
const trimmed = trimSessions(next, { limit: input.store.limit, permission: input.store.permission })
input.setStore("session", reconcile(trimmed, { key: "id" }))
+ cleanupDroppedSessionCaches(input.store, input.setStore, trimmed, input.setSessionTodo)
break
}
case "session.deleted": {
@@ -143,7 +153,7 @@ export function applyDirectoryEvent(input: {
}),
)
}
- cleanupSessionCaches(input.store, input.setStore, info.id, input.setSessionTodo)
+ cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo)
if (info.parentID) break
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
break
diff --git a/packages/app/src/context/global-sync/session-cache.test.ts b/packages/app/src/context/global-sync/session-cache.test.ts
new file mode 100644
index 0000000000..8e11110e3d
--- /dev/null
+++ b/packages/app/src/context/global-sync/session-cache.test.ts
@@ -0,0 +1,102 @@
+import { describe, expect, test } from "bun:test"
+import type {
+ FileDiff,
+ Message,
+ Part,
+ PermissionRequest,
+ QuestionRequest,
+ SessionStatus,
+ Todo,
+} from "@opencode-ai/sdk/v2/client"
+import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
+
+const msg = (id: string, sessionID: string) =>
+ ({
+ id,
+ sessionID,
+ role: "user",
+ time: { created: 1 },
+ agent: "assistant",
+ model: { providerID: "openai", modelID: "gpt" },
+ }) as Message
+
+const part = (id: string, sessionID: string, messageID: string) =>
+ ({
+ id,
+ sessionID,
+ messageID,
+ type: "text",
+ text: id,
+ }) as Part
+
+describe("app session cache", () => {
+ test("dropSessionCaches clears orphaned parts without message rows", () => {
+ const store: {
+ session_status: Record
+ session_diff: Record
+ todo: Record
+ message: Record
+ part: Record
+ permission: Record
+ question: Record
+ } = {
+ session_status: { ses_1: { type: "busy" } as SessionStatus },
+ session_diff: { ses_1: [] },
+ todo: { ses_1: [] as Todo[] },
+ message: {},
+ part: { msg_1: [part("prt_1", "ses_1", "msg_1")] },
+ permission: { ses_1: [] as PermissionRequest[] },
+ question: { ses_1: [] as QuestionRequest[] },
+ }
+
+ dropSessionCaches(store, ["ses_1"])
+
+ expect(store.message.ses_1).toBeUndefined()
+ expect(store.part.msg_1).toBeUndefined()
+ expect(store.todo.ses_1).toBeUndefined()
+ expect(store.session_diff.ses_1).toBeUndefined()
+ expect(store.session_status.ses_1).toBeUndefined()
+ expect(store.permission.ses_1).toBeUndefined()
+ expect(store.question.ses_1).toBeUndefined()
+ })
+
+ test("dropSessionCaches clears message-backed parts", () => {
+ const m = msg("msg_1", "ses_1")
+ const store: {
+ session_status: Record
+ session_diff: Record
+ todo: Record
+ message: Record
+ part: Record
+ permission: Record
+ question: Record
+ } = {
+ session_status: {},
+ session_diff: {},
+ todo: {},
+ message: { ses_1: [m] },
+ part: { [m.id]: [part("prt_1", "ses_1", m.id)] },
+ permission: {},
+ question: {},
+ }
+
+ dropSessionCaches(store, ["ses_1"])
+
+ expect(store.message.ses_1).toBeUndefined()
+ expect(store.part[m.id]).toBeUndefined()
+ })
+
+ test("pickSessionCacheEvictions preserves requested sessions", () => {
+ const seen = new Set(["ses_1", "ses_2", "ses_3"])
+
+ const stale = pickSessionCacheEvictions({
+ seen,
+ keep: "ses_4",
+ limit: 2,
+ preserve: ["ses_1"],
+ })
+
+ expect(stale).toEqual(["ses_2", "ses_3"])
+ expect([...seen]).toEqual(["ses_1", "ses_4"])
+ })
+})
diff --git a/packages/app/src/context/global-sync/session-cache.ts b/packages/app/src/context/global-sync/session-cache.ts
new file mode 100644
index 0000000000..0177ebbe13
--- /dev/null
+++ b/packages/app/src/context/global-sync/session-cache.ts
@@ -0,0 +1,62 @@
+import type {
+ FileDiff,
+ Message,
+ Part,
+ PermissionRequest,
+ QuestionRequest,
+ SessionStatus,
+ Todo,
+} from "@opencode-ai/sdk/v2/client"
+
+export const SESSION_CACHE_LIMIT = 40
+
+type SessionCache = {
+ session_status: Record
+ session_diff: Record
+ todo: Record
+ message: Record
+ part: Record
+ permission: Record
+ question: Record
+}
+
+export function dropSessionCaches(store: SessionCache, sessionIDs: Iterable) {
+ const stale = new Set(Array.from(sessionIDs).filter(Boolean))
+ if (stale.size === 0) return
+
+ for (const key of Object.keys(store.part)) {
+ const parts = store.part[key]
+ if (!parts?.some((part) => stale.has(part?.sessionID ?? ""))) continue
+ delete store.part[key]
+ }
+
+ for (const sessionID of stale) {
+ delete store.message[sessionID]
+ delete store.todo[sessionID]
+ delete store.session_diff[sessionID]
+ delete store.session_status[sessionID]
+ delete store.permission[sessionID]
+ delete store.question[sessionID]
+ }
+}
+
+export function pickSessionCacheEvictions(input: {
+ seen: Set
+ keep: string
+ limit: number
+ preserve?: Iterable
+}) {
+ const stale: string[] = []
+ const keep = new Set([input.keep, ...Array.from(input.preserve ?? [])])
+ if (input.seen.has(input.keep)) input.seen.delete(input.keep)
+ input.seen.add(input.keep)
+ for (const id of input.seen) {
+ if (input.seen.size - stale.length <= input.limit) break
+ if (keep.has(id)) continue
+ stale.push(id)
+ }
+ for (const id of stale) {
+ input.seen.delete(id)
+ }
+ return stale
+}
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx
index 905305d3af..b1edd541c3 100644
--- a/packages/app/src/context/language.tsx
+++ b/packages/app/src/context/language.tsx
@@ -19,6 +19,7 @@ import { dict as no } from "@/i18n/no"
import { dict as br } from "@/i18n/br"
import { dict as th } from "@/i18n/th"
import { dict as bs } from "@/i18n/bs"
+import { dict as tr } from "@/i18n/tr"
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
@@ -35,6 +36,7 @@ import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
+import { dict as uiTr } from "@opencode-ai/ui/i18n/tr"
export type Locale =
| "en"
@@ -53,6 +55,7 @@ export type Locale =
| "br"
| "th"
| "bs"
+ | "tr"
type RawDictionary = typeof en & typeof uiEn
type Dictionary = i18n.Flatten
@@ -78,8 +81,29 @@ const LOCALES: readonly Locale[] = [
"no",
"br",
"th",
+ "tr",
]
+const INTL: Record = {
+ en: "en",
+ zh: "zh-Hans",
+ zht: "zh-Hant",
+ ko: "ko",
+ de: "de",
+ es: "es",
+ fr: "fr",
+ da: "da",
+ ja: "ja",
+ pl: "pl",
+ ru: "ru",
+ ar: "ar",
+ no: "nb-NO",
+ br: "pt-BR",
+ th: "th",
+ bs: "bs",
+ tr: "tr",
+}
+
const LABEL_KEY: Record = {
en: "language.en",
zh: "language.zh",
@@ -97,6 +121,7 @@ const LABEL_KEY: Record = {
br: "language.br",
th: "language.th",
bs: "language.bs",
+ tr: "language.tr",
}
const base = i18n.flatten({ ...en, ...uiEn })
@@ -117,9 +142,11 @@ const DICT: Record = {
br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
+ tr: { ...base, ...i18n.flatten({ ...tr, ...uiTr }) },
}
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
+ { locale: "en", match: (language) => language.startsWith("en") },
{ locale: "zht", match: (language) => language.startsWith("zh") && language.includes("hant") },
{ locale: "zh", match: (language) => language.startsWith("zh") },
{ locale: "ko", match: (language) => language.startsWith("ko") },
@@ -138,6 +165,7 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
{ locale: "br", match: (language) => language.startsWith("pt") },
{ locale: "th", match: (language) => language.startsWith("th") },
{ locale: "bs", match: (language) => language.startsWith("bs") },
+ { locale: "tr", match: (language) => language.startsWith("tr") },
]
type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
@@ -157,6 +185,7 @@ const PARITY_CHECK: Record, Record> = {
br,
th,
bs,
+ tr,
}
void PARITY_CHECK
@@ -189,6 +218,8 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
)
const locale = createMemo(() => normalizeLocale(store.locale))
+ console.log("locale", locale())
+ const intl = createMemo(() => INTL[locale()])
const dict = createMemo(() => DICT[locale()])
@@ -205,6 +236,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
return {
ready,
locale,
+ intl,
locales: LOCALES,
label,
t,
diff --git a/packages/app/src/context/layout-scroll.test.ts b/packages/app/src/context/layout-scroll.test.ts
index 2a13e40204..483be150f6 100644
--- a/packages/app/src/context/layout-scroll.test.ts
+++ b/packages/app/src/context/layout-scroll.test.ts
@@ -41,4 +41,24 @@ describe("createScrollPersistence", () => {
vi.useRealTimers()
}
})
+
+ test("reseeds empty cache after persisted snapshot loads", () => {
+ const snapshot = {
+ session: {},
+ } as Record>
+
+ const scroll = createScrollPersistence({
+ getSnapshot: (sessionKey) => snapshot[sessionKey],
+ onFlush: () => {},
+ })
+
+ expect(scroll.scroll("session", "review")).toBeUndefined()
+
+ snapshot.session = {
+ review: { x: 12, y: 34 },
+ }
+
+ expect(scroll.scroll("session", "review")).toEqual({ x: 12, y: 34 })
+ scroll.dispose()
+ })
})
diff --git a/packages/app/src/context/layout-scroll.ts b/packages/app/src/context/layout-scroll.ts
index 30b0f69044..ef66eccd90 100644
--- a/packages/app/src/context/layout-scroll.ts
+++ b/packages/app/src/context/layout-scroll.ts
@@ -33,8 +33,16 @@ export function createScrollPersistence(opts: Options) {
}
function seed(sessionKey: string) {
- if (cache[sessionKey]) return
- setCache(sessionKey, clone(opts.getSnapshot(sessionKey)))
+ const next = clone(opts.getSnapshot(sessionKey))
+ const current = cache[sessionKey]
+ if (!current) {
+ setCache(sessionKey, next)
+ return
+ }
+
+ if (Object.keys(current).length > 0) return
+ if (Object.keys(next).length === 0) return
+ setCache(sessionKey, next)
}
function scroll(sessionKey: string, tab: string) {
diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx
index 71f0294e7e..5199e5a26b 100644
--- a/packages/app/src/context/layout.tsx
+++ b/packages/app/src/context/layout.tsx
@@ -7,8 +7,10 @@ import { useServer } from "./server"
import { usePlatform } from "./platform"
import { Project } from "@opencode-ai/sdk/v2"
import { Persist, persisted, removePersisted } from "@/utils/persist"
+import { decode64 } from "@/utils/base64"
import { same } from "@/utils/same"
import { createScrollPersistence, type SessionScroll } from "./layout-scroll"
+import { createPathHelpers } from "./file/path"
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
const DEFAULT_PANEL_WIDTH = 344
@@ -96,6 +98,38 @@ function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string):
return { all, active: tab }
}
+const sessionPath = (key: string) => {
+ const dir = key.split("/")[0]
+ if (!dir) return
+ const root = decode64(dir)
+ if (!root) return
+ return createPathHelpers(() => root)
+}
+
+const normalizeSessionTab = (path: ReturnType | undefined, tab: string) => {
+ if (!tab.startsWith("file://")) return tab
+ if (!path) return tab
+ return path.tab(tab)
+}
+
+const normalizeSessionTabList = (path: ReturnType | undefined, all: string[]) => {
+ const seen = new Set()
+ return all.flatMap((tab) => {
+ const value = normalizeSessionTab(path, tab)
+ if (seen.has(value)) return []
+ seen.add(value)
+ return [value]
+ })
+}
+
+const normalizeStoredSessionTabs = (key: string, tabs: SessionTabs) => {
+ const path = sessionPath(key)
+ return {
+ all: normalizeSessionTabList(path, tabs.all),
+ active: tabs.active ? normalizeSessionTab(path, tabs.active) : tabs.active,
+ }
+}
+
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
init: () => {
@@ -147,12 +181,46 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
})()
- if (migratedSidebar === sidebar && migratedReview === review && migratedFileTree === fileTree) return value
+ const sessionTabs = value.sessionTabs
+ const migratedSessionTabs = (() => {
+ if (!isRecord(sessionTabs)) return sessionTabs
+
+ let changed = false
+ const next = Object.fromEntries(
+ Object.entries(sessionTabs).map(([key, tabs]) => {
+ if (!isRecord(tabs) || !Array.isArray(tabs.all)) return [key, tabs]
+
+ const current = {
+ all: tabs.all.filter((tab): tab is string => typeof tab === "string"),
+ active: typeof tabs.active === "string" ? tabs.active : undefined,
+ }
+ const normalized = normalizeStoredSessionTabs(key, current)
+ if (current.all.length !== tabs.all.length) changed = true
+ if (!same(current.all, normalized.all) || current.active !== normalized.active) changed = true
+ if (tabs.active !== undefined && typeof tabs.active !== "string") changed = true
+ return [key, normalized]
+ }),
+ )
+
+ if (!changed) return sessionTabs
+ return next
+ })()
+
+ if (
+ migratedSidebar === sidebar &&
+ migratedReview === review &&
+ migratedFileTree === fileTree &&
+ migratedSessionTabs === sessionTabs
+ ) {
+ return value
+ }
+
return {
...value,
sidebar: migratedSidebar,
review: migratedReview,
fileTree: migratedFileTree,
+ sessionTabs: migratedSessionTabs,
}
}
@@ -745,22 +813,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
tabs(sessionKey: string | Accessor) {
const key = createSessionKeyReader(sessionKey, ensureKey)
+ const path = createMemo(() => sessionPath(key()))
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
+ const normalize = (tab: string) => normalizeSessionTab(path(), tab)
+ const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all)
return {
tabs,
active: createMemo(() => tabs().active),
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
setActive(tab: string | undefined) {
const session = key()
+ const next = tab ? normalize(tab) : tab
if (!store.sessionTabs[session]) {
- setStore("sessionTabs", session, { all: [], active: tab })
+ setStore("sessionTabs", session, { all: [], active: next })
} else {
- setStore("sessionTabs", session, "active", tab)
+ setStore("sessionTabs", session, "active", next)
}
},
setAll(all: string[]) {
const session = key()
- const next = all.filter((tab) => tab !== "review")
+ const next = normalizeAll(all).filter((tab) => tab !== "review")
if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: next, active: undefined })
} else {
@@ -769,7 +841,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
async open(tab: string) {
const session = key()
- const next = nextSessionTabsForOpen(store.sessionTabs[session], tab)
+ const next = nextSessionTabsForOpen(store.sessionTabs[session], normalize(tab))
setStore("sessionTabs", session, next)
},
close(tab: string) {
diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx
index ac5da60e86..75d1334a5a 100644
--- a/packages/app/src/context/local.tsx
+++ b/packages/app/src/context/local.tsx
@@ -35,6 +35,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const agent = (() => {
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
+ const models = useModels()
+
const [store, setStore] = createStore<{
current?: string
}>({
@@ -53,11 +55,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
setStore("current", undefined)
return
}
- if (name && available.some((x) => x.name === name)) {
- setStore("current", name)
- return
- }
- setStore("current", available[0].name)
+ const match = name ? available.find((x) => x.name === name) : undefined
+ const value = match ?? available[0]
+ if (!value) return
+ setStore("current", value.name)
+ if (!value.model) return
+ setModel({
+ providerID: value.model.providerID,
+ modelID: value.model.modelID,
+ })
+ if (value.variant)
+ models.variant.set({ providerID: value.model.providerID, modelID: value.model.modelID }, value.variant)
},
move(direction: 1 | -1) {
const available = list()
@@ -71,11 +79,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const value = available[next]
if (!value) return
setStore("current", value.name)
- if (value.model)
- setModel({
- providerID: value.model.providerID,
- modelID: value.model.modelID,
- })
+ if (!value.model) return
+ setModel({
+ providerID: value.model.providerID,
+ modelID: value.model.modelID,
+ })
+ if (value.variant)
+ models.variant.set({ providerID: value.model.providerID, modelID: value.model.modelID }, value.variant)
},
}
})()
diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts
new file mode 100644
index 0000000000..7556113005
--- /dev/null
+++ b/packages/app/src/context/permission-auto-respond.test.ts
@@ -0,0 +1,102 @@
+import { describe, expect, test } from "bun:test"
+import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client"
+import { base64Encode } from "@opencode-ai/util/encode"
+import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond"
+
+const session = (input: { id: string; parentID?: string }) =>
+ ({
+ id: input.id,
+ parentID: input.parentID,
+ }) as Session
+
+const permission = (sessionID: string) =>
+ ({
+ sessionID,
+ }) as Pick
+
+describe("autoRespondsPermission", () => {
+ test("uses a parent session's directory-scoped auto-accept", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/root`]: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(true)
+ })
+
+ test("uses a parent session's legacy auto-accept key", () => {
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+
+ expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true)
+ })
+
+ test("defaults to requiring approval when no lineage override exists", () => {
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
+ const autoAccept = {
+ other: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(false)
+ })
+
+ test("inherits a parent session's false override", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/root`]: false,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(false)
+ })
+
+ test("prefers a child override over parent override", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/root`]: false,
+ [`${base64Encode(directory)}/child`]: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("child"), directory)).toBe(true)
+ })
+
+ test("falls back to directory-level auto-accept", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/*`]: true,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("root"), directory)).toBe(true)
+ })
+
+ test("session-level override takes precedence over directory-level", () => {
+ const directory = "/tmp/project"
+ const sessions = [session({ id: "root" })]
+ const autoAccept = {
+ [`${base64Encode(directory)}/*`]: true,
+ [`${base64Encode(directory)}/root`]: false,
+ }
+
+ expect(autoRespondsPermission(autoAccept, sessions, permission("root"), directory)).toBe(false)
+ })
+})
+
+describe("isDirectoryAutoAccepting", () => {
+ test("returns true when directory key is set", () => {
+ const directory = "/tmp/project"
+ const autoAccept = { [`${base64Encode(directory)}/*`]: true }
+ expect(isDirectoryAutoAccepting(autoAccept, directory)).toBe(true)
+ })
+
+ test("returns false when directory key is not set", () => {
+ expect(isDirectoryAutoAccepting({}, "/tmp/project")).toBe(false)
+ })
+
+ test("returns false when directory key is explicitly false", () => {
+ const directory = "/tmp/project"
+ const autoAccept = { [`${base64Encode(directory)}/*`]: false }
+ expect(isDirectoryAutoAccepting(autoAccept, directory)).toBe(false)
+ })
+})
diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts
new file mode 100644
index 0000000000..b206deedff
--- /dev/null
+++ b/packages/app/src/context/permission-auto-respond.ts
@@ -0,0 +1,51 @@
+import { base64Encode } from "@opencode-ai/util/encode"
+
+export function acceptKey(sessionID: string, directory?: string) {
+ if (!directory) return sessionID
+ return `${base64Encode(directory)}/${sessionID}`
+}
+
+export function directoryAcceptKey(directory: string) {
+ return `${base64Encode(directory)}/*`
+}
+
+function accepted(autoAccept: Record, sessionID: string, directory?: string) {
+ const key = acceptKey(sessionID, directory)
+ const directoryKey = directory ? directoryAcceptKey(directory) : undefined
+ return autoAccept[key] ?? autoAccept[sessionID] ?? (directoryKey ? autoAccept[directoryKey] : undefined)
+}
+
+export function isDirectoryAutoAccepting(autoAccept: Record, directory: string) {
+ const key = directoryAcceptKey(directory)
+ return autoAccept[key] ?? false
+}
+
+function sessionLineage(session: { id: string; parentID?: string }[], sessionID: string) {
+ const parent = session.reduce((acc, item) => {
+ if (item.parentID) acc.set(item.id, item.parentID)
+ return acc
+ }, new Map())
+ const seen = new Set([sessionID])
+ const ids = [sessionID]
+
+ for (const id of ids) {
+ const parentID = parent.get(id)
+ if (!parentID || seen.has(parentID)) continue
+ seen.add(parentID)
+ ids.push(parentID)
+ }
+
+ return ids
+}
+
+export function autoRespondsPermission(
+ autoAccept: Record,
+ session: { id: string; parentID?: string }[],
+ permission: { sessionID: string },
+ directory?: string,
+) {
+ const value = sessionLineage(session, permission.sessionID)
+ .map((id) => accepted(autoAccept, id, directory))
+ .find((item): item is boolean => item !== undefined)
+ return value ?? false
+}
diff --git a/packages/app/src/context/permission.tsx b/packages/app/src/context/permission.tsx
index 988723834f..672f84f82a 100644
--- a/packages/app/src/context/permission.tsx
+++ b/packages/app/src/context/permission.tsx
@@ -1,4 +1,4 @@
-import { createMemo, onCleanup } from "solid-js"
+import { createEffect, createMemo, onCleanup } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import type { PermissionRequest } from "@opencode-ai/sdk/v2/client"
@@ -6,8 +6,13 @@ import { Persist, persisted } from "@/utils/persist"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "./global-sync"
import { useParams } from "@solidjs/router"
-import { base64Encode } from "@opencode-ai/util/encode"
import { decode64 } from "@/utils/base64"
+import {
+ acceptKey,
+ directoryAcceptKey,
+ isDirectoryAutoAccepting,
+ autoRespondsPermission,
+} from "./permission-auto-respond"
type PermissionRespondFn = (input: {
sessionID: string
@@ -16,10 +21,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 +41,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,12 +59,47 @@ 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,
}),
)
+ // When config has permission: "allow", auto-enable directory-level auto-accept
+ createEffect(() => {
+ if (!ready()) return
+ const directory = decode64(params.dir)
+ if (!directory) return
+ const [childStore] = globalSync.child(directory)
+ const perm = childStore.config.permission
+ if (typeof perm === "string" && perm === "allow") {
+ const key = directoryAcceptKey(directory)
+ if (store.autoAccept[key] === undefined) {
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = true
+ }),
+ )
+ }
+ }
+ })
+
const MAX_RESPONDED = 1000
const RESPONDED_TTL_MS = 60 * 60 * 1000
const responded = new Map()
@@ -105,14 +138,18 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
})
}
- function acceptKey(sessionID: string, directory?: string) {
- if (!directory) return sessionID
- return `${base64Encode(directory)}/${sessionID}`
+ function isAutoAccepting(sessionID: string, directory?: string) {
+ const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
+ return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
}
- function isAutoAccepting(sessionID: string, directory?: string) {
- const key = acceptKey(sessionID, directory)
- return store.autoAcceptEdits[key] ?? store.autoAcceptEdits[sessionID] ?? false
+ function isAutoAcceptingDirectory(directory: string) {
+ return isDirectoryAutoAccepting(store.autoAccept, directory)
+ }
+
+ function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
+ const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
+ return autoRespondsPermission(store.autoAccept, session, permission, directory)
}
function bumpEnableVersion(sessionID: string, directory?: string) {
@@ -127,20 +164,49 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
if (event?.type !== "permission.asked") return
const perm = event.properties
- if (!isAutoAccepting(perm.sessionID, e.name)) return
- if (!shouldAutoAccept(perm)) return
+ if (!shouldAutoRespond(perm, e.name)) return
respondOnce(perm, e.name)
})
onCleanup(unsubscribe)
+ function enableDirectory(directory: string) {
+ const key = directoryAcceptKey(directory)
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = true
+ }),
+ )
+
+ globalSDK.client.permission
+ .list({ directory })
+ .then((x) => {
+ if (!isAutoAcceptingDirectory(directory)) return
+ for (const perm of x.data ?? []) {
+ if (!perm?.id) continue
+ if (!shouldAutoRespond(perm, directory)) continue
+ respondOnce(perm, directory)
+ }
+ })
+ .catch(() => undefined)
+ }
+
+ function disableDirectory(directory: string) {
+ const key = directoryAcceptKey(directory)
+ setStore(
+ produce((draft) => {
+ draft.autoAccept[key] = false
+ }),
+ )
+ }
+
function enable(sessionID: string, directory: string) {
const key = acceptKey(sessionID, directory)
const version = bumpEnableVersion(sessionID, directory)
setStore(
produce((draft) => {
- draft.autoAcceptEdits[key] = true
- delete draft.autoAcceptEdits[sessionID]
+ draft.autoAccept[key] = true
+ delete draft.autoAccept[sessionID]
}),
)
@@ -151,8 +217,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
if (!isAutoAccepting(sessionID, directory)) return
for (const perm of x.data ?? []) {
if (!perm?.id) continue
- if (perm.sessionID !== sessionID) continue
- if (!shouldAutoAccept(perm)) continue
+ if (!shouldAutoRespond(perm, directory)) continue
respondOnce(perm, directory)
}
})
@@ -161,11 +226,12 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function disable(sessionID: string, directory?: string) {
bumpEnableVersion(sessionID, directory)
- const key = directory ? acceptKey(sessionID, directory) : undefined
+ const key = directory ? acceptKey(sessionID, directory) : sessionID
setStore(
produce((draft) => {
- if (key) delete draft.autoAcceptEdits[key]
- delete draft.autoAcceptEdits[sessionID]
+ draft.autoAccept[key] = false
+ if (!directory) return
+ delete draft.autoAccept[sessionID]
}),
)
}
@@ -174,9 +240,10 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
ready,
respond,
autoResponds(permission: PermissionRequest, directory?: string) {
- return isAutoAccepting(permission.sessionID, directory) && shouldAutoAccept(permission)
+ return shouldAutoRespond(permission, directory)
},
isAutoAccepting,
+ isAutoAcceptingDirectory,
toggleAutoAccept(sessionID: string, directory: string) {
if (isAutoAccepting(sessionID, directory)) {
disable(sessionID, directory)
@@ -185,6 +252,13 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
enable(sessionID, directory)
},
+ toggleAutoAcceptDirectory(directory: string) {
+ if (isAutoAcceptingDirectory(directory)) {
+ disableDirectory(directory)
+ return
+ }
+ enableDirectory(directory)
+ },
enableAutoAccept(sessionID: string, directory: string) {
if (isAutoAccepting(sessionID, directory)) return
enable(sessionID, directory)
@@ -193,6 +267,11 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
disable(sessionID, directory)
},
permissionsEnabled,
+ isPermissionAllowAll(directory: string) {
+ const [childStore] = globalSync.child(directory)
+ const perm = childStore.config.permission
+ return typeof perm === "string" && perm === "allow"
+ },
}
},
})
diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx
index 0648921051..fb82265591 100644
--- a/packages/app/src/context/prompt.tsx
+++ b/packages/app/src/context/prompt.tsx
@@ -116,6 +116,10 @@ function contextItemKey(item: ContextItem) {
return `${key}:c=${digest.slice(0, 8)}`
}
+function isCommentItem(item: ContextItem | (ContextItem & { key: string })) {
+ return item.type === "file" && !!item.comment?.trim()
+}
+
function createPromptActions(
setStore: SetStoreFunction<{
prompt: Prompt
@@ -189,6 +193,26 @@ function createPromptSession(dir: string, id: string | undefined) {
remove(key: string) {
setStore("context", "items", (items) => items.filter((x) => x.key !== key))
},
+ removeComment(path: string, commentID: string) {
+ setStore("context", "items", (items) =>
+ items.filter((item) => !(item.type === "file" && item.path === path && item.commentID === commentID)),
+ )
+ },
+ updateComment(path: string, commentID: string, next: Partial & { comment?: string }) {
+ setStore("context", "items", (items) =>
+ items.map((item) => {
+ if (item.type !== "file" || item.path !== path || item.commentID !== commentID) return item
+ const value = { ...item, ...next }
+ return { ...value, key: contextItemKey(value) }
+ }),
+ )
+ },
+ replaceComments(items: FileContextItem[]) {
+ setStore("context", "items", (current) => [
+ ...current.filter((item) => !isCommentItem(item)),
+ ...items.map((item) => ({ ...item, key: contextItemKey(item) })),
+ ])
+ },
},
set: actions.set,
reset: actions.reset,
@@ -251,6 +275,10 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
items: () => session().context.items(),
add: (item: ContextItem) => session().context.add(item),
remove: (key: string) => session().context.remove(key),
+ removeComment: (path: string, commentID: string) => session().context.removeComment(path, commentID),
+ updateComment: (path: string, commentID: string, next: Partial & { comment?: string }) =>
+ session().context.updateComment(path, commentID, next),
+ replaceComments: (items: FileContextItem[]) => session().context.replaceComments(items),
},
set: (prompt: Prompt, cursorPosition?: number) => session().set(prompt, cursorPosition),
reset: () => session().reset(),
diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx
index 3849bb6ae7..4ff777e2eb 100644
--- a/packages/app/src/context/server.tsx
+++ b/packages/app/src/context/server.tsx
@@ -6,6 +6,7 @@ import { Persist, persisted } from "@/utils/persist"
import { checkServerHealth } from "@/utils/server-health"
type StoredProject = { worktree: string; expanded: boolean }
+type StoredServer = string | ServerConnection.HttpBase | ServerConnection.Http
const HEALTH_POLL_INTERVAL_MS = 10_000
export function normalizeServerUrl(input: string) {
@@ -15,9 +16,9 @@ export function normalizeServerUrl(input: string) {
return withProtocol.replace(/\/+$/, "")
}
-export function serverDisplayName(conn?: ServerConnection.Any) {
+export function serverName(conn?: ServerConnection.Any, ignoreDisplayName = false) {
if (!conn) return ""
- if (conn.displayName) return conn.displayName
+ if (conn.displayName && !ignoreDisplayName) return conn.displayName
return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
}
@@ -100,22 +101,33 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
const [store, setStore, _, ready] = persisted(
Persist.global("server", ["server.v3"]),
createStore({
- list: [] as string[],
+ list: [] as StoredServer[],
projects: {} as Record,
lastProject: {} as Record,
}),
)
+ const url = (x: StoredServer) => (typeof x === "string" ? x : "type" in x ? x.http.url : x.url)
+
const allServers = createMemo((): Array => {
const servers = [
...(props.servers ?? []),
- ...store.list.map((value) => ({
- type: "http" as const,
- http: typeof value === "string" ? { url: value } : value,
- })),
+ ...store.list.map((value) =>
+ typeof value === "string"
+ ? {
+ type: "http" as const,
+ http: { url: value },
+ }
+ : value,
+ ),
]
- const deduped = new Map(servers.map((conn) => [ServerConnection.key(conn), conn]))
+ const deduped = new Map(
+ servers.map((value) => {
+ const conn: ServerConnection.Any = "type" in value ? value : { type: "http", http: value }
+ return [ServerConnection.key(conn), conn]
+ }),
+ )
return [...deduped.values()]
})
@@ -156,27 +168,29 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
if (state.active !== input) setState("active", input)
}
- function add(input: string) {
- const url = normalizeServerUrl(input)
- if (!url) return
+ function add(input: ServerConnection.Http) {
+ const url_ = normalizeServerUrl(input.http.url)
+ if (!url_) return
+ const conn = { ...input, http: { ...input.http, url: url_ } }
return batch(() => {
- const http: ServerConnection.HttpBase = { url }
- if (!store.list.includes(url)) {
- setStore("list", store.list.length, url)
+ const existing = store.list.findIndex((x) => url(x) === url_)
+ if (existing !== -1) {
+ setStore("list", existing, conn)
+ } else {
+ setStore("list", store.list.length, conn)
}
- const conn: ServerConnection.Http = { type: "http", http }
setState("active", ServerConnection.key(conn))
return conn
})
}
function remove(key: ServerConnection.Key) {
- const list = store.list.filter((x) => x !== key)
+ const list = store.list.filter((x) => url(x) !== key)
batch(() => {
setStore("list", list)
if (state.active === key) {
const next = list[0]
- setState("active", next ? ServerConnection.key({ type: "http", http: { url: next } }) : props.defaultServer)
+ setState("active", next ? ServerConnection.Key.make(url(next)) : props.defaultServer)
}
})
}
@@ -212,7 +226,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
return state.active
},
get name() {
- return serverDisplayName(current())
+ return serverName(current())
},
get list() {
return allServers()
diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx
index d279a7f321..b43469b5c3 100644
--- a/packages/app/src/context/settings.tsx
+++ b/packages/app/src/context/settings.tsx
@@ -23,6 +23,8 @@ export interface Settings {
autoSave: boolean
releaseNotes: boolean
showReasoningSummaries: boolean
+ shellToolPartsExpanded: boolean
+ editToolPartsExpanded: boolean
}
updates: {
startup: boolean
@@ -44,6 +46,8 @@ const defaultSettings: Settings = {
autoSave: true,
releaseNotes: true,
showReasoningSummaries: false,
+ shellToolPartsExpanded: true,
+ editToolPartsExpanded: false,
},
updates: {
startup: true,
@@ -129,6 +133,20 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setShowReasoningSummaries(value: boolean) {
setStore("general", "showReasoningSummaries", value)
},
+ shellToolPartsExpanded: withFallback(
+ () => store.general?.shellToolPartsExpanded,
+ defaultSettings.general.shellToolPartsExpanded,
+ ),
+ setShellToolPartsExpanded(value: boolean) {
+ setStore("general", "shellToolPartsExpanded", value)
+ },
+ editToolPartsExpanded: withFallback(
+ () => store.general?.editToolPartsExpanded,
+ defaultSettings.general.editToolPartsExpanded,
+ ),
+ setEditToolPartsExpanded(value: boolean) {
+ setStore("general", "editToolPartsExpanded", value)
+ },
},
updates: {
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx
index 60888b1a6f..5623a2c7cd 100644
--- a/packages/app/src/context/sync.tsx
+++ b/packages/app/src/context/sync.tsx
@@ -6,6 +6,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSync } from "./global-sync"
import { useSDK } from "./sdk"
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
+import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
function sortParts(parts: Part[]) {
return parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
@@ -43,12 +44,11 @@ type OptimisticRemoveInput = {
export function applyOptimisticAdd(draft: OptimisticStore, input: OptimisticAddInput) {
const messages = draft.message[input.sessionID]
- if (!messages) {
- draft.message[input.sessionID] = [input.message]
- }
if (messages) {
const result = Binary.search(messages, input.message.id, (m) => m.id)
messages.splice(result.index, 0, input.message)
+ } else {
+ draft.message[input.sessionID] = [input.message]
}
draft.part[input.message.id] = sortParts(input.parts)
}
@@ -105,10 +105,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
return globalSync.child(directory)
}
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
- const messagePageSize = 400
+ const messagePageSize = 200
const inflight = new Map>()
const inflightDiff = new Map>()
const inflightTodo = new Map>()
+ const maxDirs = 30
+ const seen = new Map>()
const [meta, setMeta] = createStore({
limit: {} as Record,
complete: {} as Record,
@@ -122,9 +124,60 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
return undefined
}
- const limitFor = (count: number) => {
- if (count <= messagePageSize) return messagePageSize
- return Math.ceil(count / messagePageSize) * messagePageSize
+ const seenFor = (directory: string) => {
+ const existing = seen.get(directory)
+ if (existing) {
+ seen.delete(directory)
+ seen.set(directory, existing)
+ return existing
+ }
+ const created = new Set()
+ seen.set(directory, created)
+ while (seen.size > maxDirs) {
+ const first = seen.keys().next().value
+ if (!first) break
+ const stale = [...(seen.get(first) ?? [])]
+ seen.delete(first)
+ const [, setStore] = globalSync.child(first, { bootstrap: false })
+ evict(first, setStore, stale)
+ }
+ return created
+ }
+
+ const clearMeta = (directory: string, sessionIDs: string[]) => {
+ if (sessionIDs.length === 0) return
+ setMeta(
+ produce((draft) => {
+ for (const sessionID of sessionIDs) {
+ const key = keyFor(directory, sessionID)
+ delete draft.limit[key]
+ delete draft.complete[key]
+ delete draft.loading[key]
+ }
+ }),
+ )
+ }
+
+ const evict = (directory: string, setStore: Setter, sessionIDs: string[]) => {
+ if (sessionIDs.length === 0) return
+ for (const sessionID of sessionIDs) {
+ globalSync.todo.set(sessionID, undefined)
+ }
+ setStore(
+ produce((draft) => {
+ dropSessionCaches(draft, sessionIDs)
+ }),
+ )
+ clearMeta(directory, sessionIDs)
+ }
+
+ const touch = (directory: string, setStore: Setter, sessionID: string) => {
+ const stale = pickSessionCacheEvictions({
+ seen: seenFor(directory),
+ keep: sessionID,
+ limit: SESSION_CACHE_LIMIT,
+ })
+ evict(directory, setStore, stale)
}
const fetchMessages = async (input: { client: typeof sdk.client; sessionID: string; limit: number }) => {
@@ -132,10 +185,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
input.client.session.messages({ sessionID: input.sessionID, limit: input.limit }),
)
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
- const session = items
- .map((x) => x.info)
- .filter((m) => !!m?.id)
- .sort((a, b) => cmp(a.id, b.id))
+ const session = items.map((x) => x.info).sort((a, b) => cmp(a.id, b.id))
const part = items.map((message) => ({ id: message.info.id, part: sortParts(message.parts) }))
return {
session,
@@ -144,6 +194,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
}
+ const tracked = (directory: string, sessionID: string) => seen.get(directory)?.has(sessionID) ?? false
+
const loadMessages = async (input: {
directory: string
client: typeof sdk.client
@@ -157,16 +209,18 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
setMeta("loading", key, true)
await fetchMessages(input)
.then((next) => {
+ if (!tracked(input.directory, input.sessionID)) return
batch(() => {
input.setStore("message", input.sessionID, reconcile(next.session, { key: "id" }))
- for (const message of next.part) {
- input.setStore("part", message.id, reconcile(message.part, { key: "id" }))
+ for (const p of next.part) {
+ input.setStore("part", p.id, p.part)
}
setMeta("limit", key, input.limit)
setMeta("complete", key, next.complete)
})
})
.finally(() => {
+ if (!tracked(input.directory, input.sessionID)) return
setMeta("loading", key, false)
})
}
@@ -208,6 +262,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
parts: Part[]
agent: string
model: { providerID: string; modelID: string }
+ variant?: string
}) {
const message: Message = {
id: input.messageID,
@@ -216,6 +271,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
time: { created: Date.now() },
agent: input.agent,
model: input.model,
+ variant: input.variant,
}
const [, setStore] = target()
setOptimisticAdd(setStore as (...args: unknown[]) => void, {
@@ -229,21 +285,18 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
const key = keyFor(directory, sessionID)
- const hasSession = (() => {
- const match = Binary.search(store.session, sessionID, (s) => s.id)
- return match.found
- })()
+ const hasSession = Binary.search(store.session, sessionID, (s) => s.id).found
- const hasMessages = store.message[sessionID] !== undefined
- const hydrated = meta.limit[key] !== undefined
- if (hasSession && hasMessages && hydrated) return
+ touch(directory, setStore, sessionID)
- const count = store.message[sessionID]?.length ?? 0
- const limit = hydrated ? (meta.limit[key] ?? messagePageSize) : limitFor(count)
+ if (store.message[sessionID] !== undefined && hasSession && meta.limit[key] !== undefined) return
+
+ const limit = meta.limit[key] ?? messagePageSize
const sessionReq = hasSession
? Promise.resolve()
: retry(() => client.session.get({ sessionID })).then((session) => {
+ if (!tracked(directory, sessionID)) return
const data = session.data
if (!data) return
setStore(
@@ -259,16 +312,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
)
})
- const messagesReq =
- hasMessages && hydrated
- ? Promise.resolve()
- : loadMessages({
- directory,
- client,
- setStore,
- sessionID,
- limit,
- })
+ const messagesReq = loadMessages({
+ directory,
+ client,
+ setStore,
+ sessionID,
+ limit,
+ })
return runInflight(inflight, key, () => Promise.all([sessionReq, messagesReq]).then(() => {}))
},
@@ -276,11 +326,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
+ touch(directory, setStore, sessionID)
if (store.session_diff[sessionID] !== undefined) return
const key = keyFor(directory, sessionID)
return runInflight(inflightDiff, key, () =>
retry(() => client.session.diff({ sessionID })).then((diff) => {
+ if (!tracked(directory, sessionID)) return
setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
}),
)
@@ -289,15 +341,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const directory = sdk.directory
const client = sdk.client
const [store, setStore] = globalSync.child(directory)
+ touch(directory, setStore, sessionID)
const existing = store.todo[sessionID]
+ const cached = globalSync.data.session_todo[sessionID]
if (existing !== undefined) {
- if (globalSync.data.session_todo[sessionID] === undefined) {
+ if (cached === undefined) {
globalSync.todo.set(sessionID, existing)
}
return
}
- const cached = globalSync.data.session_todo[sessionID]
if (cached !== undefined) {
setStore("todo", sessionID, reconcile(cached, { key: "id" }))
}
@@ -305,6 +358,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const key = keyFor(directory, sessionID)
return runInflight(inflightTodo, key, () =>
retry(() => client.session.todo({ sessionID })).then((todo) => {
+ if (!tracked(directory, sessionID)) return
const list = todo.data ?? []
setStore("todo", sessionID, reconcile(list, { key: "id" }))
globalSync.todo.set(sessionID, list)
@@ -324,11 +378,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const key = keyFor(sdk.directory, sessionID)
return meta.loading[key] ?? false
},
- async loadMore(sessionID: string, count = messagePageSize) {
+ async loadMore(sessionID: string, count?: number) {
const directory = sdk.directory
const client = sdk.client
const [, setStore] = globalSync.child(directory)
+ touch(directory, setStore, sessionID)
const key = keyFor(directory, sessionID)
+ const step = count ?? messagePageSize
if (meta.loading[key]) return
if (meta.complete[key]) return
@@ -338,10 +394,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
client,
setStore,
sessionID,
- limit: currentLimit + count,
+ limit: currentLimit + step,
})
},
},
+ evict(sessionID: string, directory = sdk.directory) {
+ const [, setStore] = globalSync.child(directory)
+ seenFor(directory).delete(sessionID)
+ evict(directory, setStore, [sessionID])
+ },
fetch: async (count = 10) => {
const directory = sdk.directory
const client = sdk.client
diff --git a/packages/app/src/context/terminal.test.ts b/packages/app/src/context/terminal.test.ts
index a250de57c0..6e07e03124 100644
--- a/packages/app/src/context/terminal.test.ts
+++ b/packages/app/src/context/terminal.test.ts
@@ -2,6 +2,7 @@ import { beforeAll, describe, expect, mock, test } from "bun:test"
let getWorkspaceTerminalCacheKey: (dir: string) => string
let getLegacyTerminalStorageKeys: (dir: string, legacySessionID?: string) => string[]
+let migrateTerminalState: (value: unknown) => unknown
beforeAll(async () => {
mock.module("@solidjs/router", () => ({
@@ -17,6 +18,7 @@ beforeAll(async () => {
const mod = await import("./terminal")
getWorkspaceTerminalCacheKey = mod.getWorkspaceTerminalCacheKey
getLegacyTerminalStorageKeys = mod.getLegacyTerminalStorageKeys
+ migrateTerminalState = mod.migrateTerminalState
})
describe("getWorkspaceTerminalCacheKey", () => {
@@ -37,3 +39,44 @@ describe("getLegacyTerminalStorageKeys", () => {
])
})
})
+
+describe("migrateTerminalState", () => {
+ test("drops invalid terminals and restores a valid active terminal", () => {
+ expect(
+ migrateTerminalState({
+ active: "missing",
+ all: [
+ null,
+ { id: "one", title: "Terminal 2" },
+ { id: "one", title: "duplicate", titleNumber: 9 },
+ { id: "two", title: "logs", titleNumber: 4, rows: 24, cols: 80 },
+ { title: "no-id" },
+ ],
+ }),
+ ).toEqual({
+ active: "one",
+ all: [
+ { id: "one", title: "Terminal 2", titleNumber: 2 },
+ { id: "two", title: "logs", titleNumber: 4, rows: 24, cols: 80 },
+ ],
+ })
+ })
+
+ test("keeps a valid active id", () => {
+ expect(
+ migrateTerminalState({
+ active: "two",
+ all: [
+ { id: "one", title: "Terminal 1" },
+ { id: "two", title: "shell", titleNumber: 7 },
+ ],
+ }),
+ ).toEqual({
+ active: "two",
+ all: [
+ { id: "one", title: "Terminal 1", titleNumber: 1 },
+ { id: "two", title: "shell", titleNumber: 7 },
+ ],
+ })
+ })
+})
diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx
index 64f026219a..a2807375ff 100644
--- a/packages/app/src/context/terminal.tsx
+++ b/packages/app/src/context/terminal.tsx
@@ -1,6 +1,6 @@
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
-import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
+import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
import type { Platform } from "./platform"
@@ -20,6 +20,71 @@ export type LocalPTY = {
const WORKSPACE_KEY = "__workspace__"
const MAX_TERMINAL_SESSIONS = 20
+function record(value: unknown): value is Record {
+ return typeof value === "object" && value !== null && !Array.isArray(value)
+}
+
+function text(value: unknown) {
+ return typeof value === "string" ? value : undefined
+}
+
+function num(value: unknown) {
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined
+}
+
+function numberFromTitle(title: string) {
+ const match = title.match(/^Terminal (\d+)$/)
+ if (!match) return
+ const value = Number(match[1])
+ if (!Number.isFinite(value) || value <= 0) return
+ return value
+}
+
+function pty(value: unknown): LocalPTY | undefined {
+ if (!record(value)) return
+
+ const id = text(value.id)
+ if (!id) return
+
+ const title = text(value.title) ?? ""
+ const number = num(value.titleNumber)
+ const rows = num(value.rows)
+ const cols = num(value.cols)
+ const buffer = text(value.buffer)
+ const scrollY = num(value.scrollY)
+ const cursor = num(value.cursor)
+
+ return {
+ id,
+ title,
+ titleNumber: number && number > 0 ? number : (numberFromTitle(title) ?? 0),
+ ...(rows !== undefined ? { rows } : {}),
+ ...(cols !== undefined ? { cols } : {}),
+ ...(buffer !== undefined ? { buffer } : {}),
+ ...(scrollY !== undefined ? { scrollY } : {}),
+ ...(cursor !== undefined ? { cursor } : {}),
+ }
+}
+
+export function migrateTerminalState(value: unknown) {
+ if (!record(value)) return value
+
+ const seen = new Set()
+ const all = (Array.isArray(value.all) ? value.all : []).flatMap((item) => {
+ const next = pty(item)
+ if (!next || seen.has(next.id)) return []
+ seen.add(next.id)
+ return [next]
+ })
+
+ const active = text(value.active)
+
+ return {
+ active: active && seen.has(active) ? active : all[0]?.id,
+ all,
+ }
+}
+
export function getWorkspaceTerminalCacheKey(dir: string) {
return `${dir}:${WORKSPACE_KEY}`
}
@@ -38,6 +103,16 @@ type TerminalCacheEntry = {
const caches = new Set>()
+const trimTerminal = (pty: LocalPTY) => {
+ if (!pty.buffer && pty.cursor === undefined && pty.scrollY === undefined) return pty
+ return {
+ ...pty,
+ buffer: undefined,
+ cursor: undefined,
+ scrollY: undefined,
+ }
+}
+
export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], platform?: Platform) {
const key = getWorkspaceTerminalCacheKey(dir)
for (const cache of caches) {
@@ -61,16 +136,11 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
function createWorkspaceTerminalSession(sdk: ReturnType, dir: string, legacySessionID?: string) {
const legacy = getLegacyTerminalStorageKeys(dir, legacySessionID)
- const numberFromTitle = (title: string) => {
- const match = title.match(/^Terminal (\d+)$/)
- if (!match) return
- const value = Number(match[1])
- if (!Number.isFinite(value) || value <= 0) return
- return value
- }
-
const [store, setStore, _, ready] = persisted(
- Persist.workspace(dir, "terminal", legacy),
+ {
+ ...Persist.workspace(dir, "terminal", legacy),
+ migrate: migrateTerminalState,
+ },
createStore<{
active?: string
all: LocalPTY[]
@@ -118,26 +188,6 @@ function createWorkspaceTerminalSession(sdk: ReturnType, dir: str
})
onCleanup(unsub)
- const meta = { migrated: false }
-
- createEffect(() => {
- if (!ready()) return
- if (meta.migrated) return
- meta.migrated = true
-
- setStore("all", (all) => {
- const next = all.map((pty) => {
- const direct = Number.isFinite(pty.titleNumber) && pty.titleNumber > 0 ? pty.titleNumber : undefined
- if (direct !== undefined) return pty
- const parsed = numberFromTitle(pty.title)
- if (parsed === undefined) return pty
- return { ...pty, titleNumber: parsed }
- })
- if (next.every((pty, index) => pty === all[index])) return all
- return next
- })
- })
-
return {
ready,
all: createMemo(() => store.all),
@@ -188,6 +238,18 @@ function createWorkspaceTerminalSession(sdk: ReturnType, dir: str
console.error("Failed to update terminal", error)
})
},
+ trim(id: string) {
+ const index = store.all.findIndex((x) => x.id === id)
+ if (index === -1) return
+ setStore("all", index, (pty) => trimTerminal(pty))
+ },
+ trimAll() {
+ setStore("all", (all) => {
+ const next = all.map(trimTerminal)
+ if (next.every((pty, index) => pty === all[index])) return all
+ return next
+ })
+ },
async clone(id: string) {
const index = store.all.findIndex((x) => x.id === id)
const pty = store.all[index]
@@ -322,12 +384,27 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
const workspace = createMemo(() => loadWorkspace(params.dir!, params.id))
+ createEffect(
+ on(
+ () => ({ dir: params.dir, id: params.id }),
+ (next, prev) => {
+ if (!prev?.dir) return
+ if (next.dir === prev.dir && next.id === prev.id) return
+ if (next.dir === prev.dir && next.id) return
+ loadWorkspace(prev.dir, prev.id).trimAll()
+ },
+ { defer: true },
+ ),
+ )
+
return {
ready: () => workspace().ready(),
all: () => workspace().all(),
active: () => workspace().active(),
new: () => workspace().new(),
update: (pty: Partial & { id: string }) => workspace().update(pty),
+ trim: (id: string) => workspace().trim(id),
+ trimAll: () => workspace().trimAll(),
clone: (id: string) => workspace().clone(id),
open: (id: string) => workspace().open(id),
close: (id: string) => workspace().close(id),
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 69a3a86cb2..c9b92db501 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": "فشل تحميل الملف",
@@ -454,6 +456,7 @@ export const dict = {
"session.todo.title": "المهام",
"session.todo.collapse": "طي",
"session.todo.expand": "توسيع",
+ "session.new.title": "ابنِ أي شيء",
"session.new.worktree.main": "الفرع الرئيسي",
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
@@ -529,6 +532,7 @@ export const dict = {
"settings.general.section.notifications": "إشعارات النظام",
"settings.general.section.updates": "التحديثات",
"settings.general.section.sounds": "المؤثرات الصوتية",
+ "settings.general.section.feed": "الخلاصة",
"settings.general.section.display": "شاشة العرض",
"settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
@@ -538,6 +542,12 @@ export const dict = {
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
"settings.general.row.font.title": "الخط",
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
+ "settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "إظهار أجزاء أداة shell موسعة بشكل افتراضي في الشريط الزمني",
+ "settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
+ "settings.general.row.editToolPartsExpanded.description":
+ "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
"settings.general.row.wayland.tooltip":
@@ -565,6 +575,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "بلا",
"sound.option.alert01": "تنبيه 01",
"sound.option.alert02": "تنبيه 02",
"sound.option.alert03": "تنبيه 03",
@@ -724,4 +735,18 @@ export const dict = {
"workspace.reset.archived.one": "ستتم أرشفة جلسة واحدة.",
"workspace.reset.archived.many": "ستتم أرشفة {{count}} جلسات.",
"workspace.reset.note": "سيؤدي هذا إلى إعادة تعيين مساحة العمل لتتطابق مع الفرع الافتراضي.",
+ "common.open": "فتح",
+ "dialog.releaseNotes.action.getStarted": "البدء",
+ "dialog.releaseNotes.action.next": "التالي",
+ "dialog.releaseNotes.action.hideFuture": "عدم إظهار هذا في المستقبل",
+ "dialog.releaseNotes.media.alt": "معاينة الإصدار",
+ "toast.project.reloadFailed.title": "فشل في إعادة تحميل {{project}}",
+ "error.server.invalidConfiguration": "تكوين غير صالح",
+ "common.moreCountSuffix": " (+{{count}} إضافي)",
+ "common.time.justNow": "الآن",
+ "common.time.minutesAgo.short": "قبل {{count}} د",
+ "common.time.hoursAgo.short": "قبل {{count}} س",
+ "common.time.daysAgo.short": "قبل {{count}} ي",
+ "settings.providers.connected.environmentDescription": "متصل من متغيرات البيئة الخاصة بك",
+ "settings.providers.custom.description": "أضف مزود متوافق مع OpenAI بواسطة عنوان URL الأساسي.",
}
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index 1c37317a37..951edf0a5c 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",
@@ -457,6 +459,7 @@ export const dict = {
"session.todo.title": "Tarefas",
"session.todo.collapse": "Recolher",
"session.todo.expand": "Expandir",
+ "session.new.title": "Crie qualquer coisa",
"session.new.worktree.main": "Branch principal",
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
"session.new.worktree.create": "Criar novo worktree",
@@ -535,6 +538,7 @@ export const dict = {
"settings.general.section.notifications": "Notificações do sistema",
"settings.general.section.updates": "Atualizações",
"settings.general.section.sounds": "Efeitos sonoros",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Tela",
"settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
@@ -544,6 +548,12 @@ export const dict = {
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
"settings.general.row.font.title": "Fonte",
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
+ "settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Mostrar partes da ferramenta shell expandidas por padrão na linha do tempo",
+ "settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
"settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
"settings.general.row.wayland.tooltip":
@@ -571,6 +581,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Nenhum",
"sound.option.alert01": "Alerta 01",
"sound.option.alert02": "Alerta 02",
"sound.option.alert03": "Alerta 03",
@@ -732,4 +743,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sessão será arquivada.",
"workspace.reset.archived.many": "{{count}} sessões serão arquivadas.",
"workspace.reset.note": "Isso redefinirá o espaço de trabalho para corresponder ao branch padrão.",
+ "common.open": "Abrir",
+ "dialog.releaseNotes.action.getStarted": "Começar",
+ "dialog.releaseNotes.action.next": "Próximo",
+ "dialog.releaseNotes.action.hideFuture": "Não mostrar isso no futuro",
+ "dialog.releaseNotes.media.alt": "Prévia do lançamento",
+ "toast.project.reloadFailed.title": "Falha ao recarregar {{project}}",
+ "error.server.invalidConfiguration": "Configuração inválida",
+ "common.moreCountSuffix": " (+{{count}} mais)",
+ "common.time.justNow": "Agora mesmo",
+ "common.time.minutesAgo.short": "{{count}}m atrás",
+ "common.time.hoursAgo.short": "{{count}}h atrás",
+ "common.time.daysAgo.short": "{{count}}d atrás",
+ "settings.providers.connected.environmentDescription": "Conectado a partir de suas variáveis de ambiente",
+ "settings.providers.custom.description": "Adicionar um provedor compatível com a OpenAI através do URL base.",
}
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index 59bab1eb8b..e8bdcde596 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,8 +99,10 @@ 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": "Claude modeli za pomoć pri kodiranju",
+ "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",
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
"dialog.provider.openrouter.note": "Pristup svim podržanim modelima preko jednog provajdera",
@@ -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",
@@ -513,6 +515,7 @@ export const dict = {
"session.todo.collapse": "Sažmi",
"session.todo.expand": "Proširi",
+ "session.new.title": "Napravi bilo šta",
"session.new.worktree.main": "Glavna grana",
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
"session.new.worktree.create": "Kreiraj novi worktree",
@@ -599,6 +602,7 @@ export const dict = {
"settings.general.section.notifications": "Sistemske obavijesti",
"settings.general.section.updates": "Ažuriranja",
"settings.general.section.sounds": "Zvučni efekti",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Prikaz",
"settings.general.row.language.title": "Jezik",
@@ -610,6 +614,12 @@ export const dict = {
"settings.general.row.font.title": "Font",
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
+ "settings.general.row.shellToolPartsExpanded.title": "Proširi dijelove shell alata",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Prikaži dijelove shell alata podrazumijevano proširene na vremenskoj traci",
+ "settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
"settings.general.row.wayland.title": "Koristi nativni Wayland",
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
"settings.general.row.wayland.tooltip":
@@ -639,6 +649,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Nijedan",
"sound.option.alert01": "Upozorenje 01",
"sound.option.alert02": "Upozorenje 02",
"sound.option.alert03": "Upozorenje 03",
@@ -809,4 +820,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesija će biti arhivirana.",
"workspace.reset.archived.many": "Biće arhivirano {{count}} sesija.",
"workspace.reset.note": "Ovo će resetovati radni prostor da odgovara podrazumijevanoj grani.",
+ "common.open": "Otvori",
+ "dialog.releaseNotes.action.getStarted": "Započni",
+ "dialog.releaseNotes.action.next": "Sljedeće",
+ "dialog.releaseNotes.action.hideFuture": "Ne prikazuj ovo u budućnosti",
+ "dialog.releaseNotes.media.alt": "Pregled izdanja",
+ "toast.project.reloadFailed.title": "Nije uspjelo ponovno učitavanje {{project}}",
+ "error.server.invalidConfiguration": "Nevažeća konfiguracija",
+ "common.moreCountSuffix": " (+{{count}} više)",
+ "common.time.justNow": "Upravo sada",
+ "common.time.minutesAgo.short": "prije {{count}} min",
+ "common.time.hoursAgo.short": "prije {{count}} h",
+ "common.time.daysAgo.short": "prije {{count}} d",
+ "settings.providers.connected.environmentDescription": "Povezano sa vašim varijablama okruženja",
+ "settings.providers.custom.description": "Dodajte provajdera kompatibilnog s OpenAI putem osnovnog URL-a.",
}
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index ce33ceec31..5ea52a5c92 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,8 +99,10 @@ 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": "Claude-modeller til kodningsassistance",
+ "dialog.provider.copilot.note": "AI-modeller til kodningsassistance via GitHub Copilot",
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
"dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar",
"dialog.provider.openrouter.note": "Få adgang til alle understøttede modeller fra én udbyder",
@@ -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",
@@ -508,6 +510,7 @@ export const dict = {
"session.todo.collapse": "Skjul",
"session.todo.expand": "Udvid",
+ "session.new.title": "Byg hvad som helst",
"session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opret nyt worktree",
@@ -594,6 +597,7 @@ export const dict = {
"settings.general.section.notifications": "Systemmeddelelser",
"settings.general.section.updates": "Opdateringer",
"settings.general.section.sounds": "Lydeffekter",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Skærm",
"settings.general.row.language.title": "Sprog",
@@ -605,6 +609,11 @@ export const dict = {
"settings.general.row.font.title": "Skrifttype",
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
+ "settings.general.row.shellToolPartsExpanded.title": "Udvid shell-værktøjsdele",
+ "settings.general.row.shellToolPartsExpanded.description": "Vis shell-værktøjsdele udvidet som standard i tidslinjen",
+ "settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
"settings.general.row.wayland.title": "Brug native Wayland",
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
"settings.general.row.wayland.tooltip":
@@ -635,6 +644,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Ingen",
"sound.option.alert01": "Alarm 01",
"sound.option.alert02": "Alarm 02",
"sound.option.alert03": "Alarm 03",
@@ -804,4 +814,18 @@ export const dict = {
"workspace.reset.archived.one": "1 session vil blive arkiveret.",
"workspace.reset.archived.many": "{{count}} sessioner vil blive arkiveret.",
"workspace.reset.note": "Dette vil nulstille arbejdsområdet til at matche hovedgrenen.",
+ "common.open": "Åbn",
+ "dialog.releaseNotes.action.getStarted": "Kom i gang",
+ "dialog.releaseNotes.action.next": "Næste",
+ "dialog.releaseNotes.action.hideFuture": "Vis ikke disse i fremtiden",
+ "dialog.releaseNotes.media.alt": "Forhåndsvisning af udgivelse",
+ "toast.project.reloadFailed.title": "Kunne ikke genindlæse {{project}}",
+ "error.server.invalidConfiguration": "Ugyldig konfiguration",
+ "common.moreCountSuffix": " (+{{count}} mere)",
+ "common.time.justNow": "Lige nu",
+ "common.time.minutesAgo.short": "{{count}}m siden",
+ "common.time.hoursAgo.short": "{{count}}t siden",
+ "common.time.daysAgo.short": "{{count}}d siden",
+ "settings.providers.connected.environmentDescription": "Tilsluttet fra dine miljøvariabler",
+ "settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.",
}
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index cf3416be2d..a6cf8045c0 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",
@@ -465,6 +467,7 @@ export const dict = {
"session.todo.title": "Aufgaben",
"session.todo.collapse": "Einklappen",
"session.todo.expand": "Ausklappen",
+ "session.new.title": "Baue, was du willst",
"session.new.worktree.main": "Haupt-Branch",
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
"session.new.worktree.create": "Neuen Worktree erstellen",
@@ -544,6 +547,7 @@ export const dict = {
"settings.general.section.notifications": "Systembenachrichtigungen",
"settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Soundeffekte",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Anzeige",
"settings.general.row.language.title": "Sprache",
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
@@ -553,6 +557,12 @@ export const dict = {
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
"settings.general.row.font.title": "Schriftart",
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
+ "settings.general.row.shellToolPartsExpanded.title": "Shell-Tool-Abschnitte ausklappen",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Shell-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
+ "settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
"settings.general.row.wayland.title": "Natives Wayland verwenden",
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
"settings.general.row.wayland.tooltip":
@@ -580,6 +590,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Keine",
"sound.option.alert01": "Alarm 01",
"sound.option.alert02": "Alarm 02",
"sound.option.alert03": "Alarm 03",
@@ -741,4 +752,18 @@ export const dict = {
"workspace.reset.archived.one": "1 Sitzung wird archiviert.",
"workspace.reset.archived.many": "{{count}} Sitzungen werden archiviert.",
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt.",
+ "common.open": "Öffnen",
+ "dialog.releaseNotes.action.getStarted": "Loslegen",
+ "dialog.releaseNotes.action.next": "Weiter",
+ "dialog.releaseNotes.action.hideFuture": "In Zukunft nicht mehr anzeigen",
+ "dialog.releaseNotes.media.alt": "Vorschau auf die Version",
+ "toast.project.reloadFailed.title": "Fehler beim Neuladen von {{project}}",
+ "error.server.invalidConfiguration": "Ungültige Konfiguration",
+ "common.moreCountSuffix": " (+{{count}} weitere)",
+ "common.time.justNow": "Gerade eben",
+ "common.time.minutesAgo.short": "vor {{count}} Min",
+ "common.time.hoursAgo.short": "vor {{count}} Std",
+ "common.time.daysAgo.short": "vor {{count}} Tg",
+ "settings.providers.connected.environmentDescription": "Verbunden aus Ihren Umgebungsvariablen",
+ "settings.providers.custom.description": "Fügen Sie einen OpenAI-kompatiblen Anbieter per Basis-URL hinzu.",
} satisfies Partial>
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index a8c27cc63e..97a572f1cf 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,8 +99,10 @@ 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": "Claude models for coding assistance",
+ "dialog.provider.copilot.note": "AI models for coding assistance via GitHub Copilot",
"dialog.provider.openai.note": "GPT models for fast, capable general AI tasks",
"dialog.provider.google.note": "Gemini models for fast, structured responses",
"dialog.provider.openrouter.note": "Access all supported models from one provider",
@@ -216,6 +218,7 @@ export const dict = {
"common.loading": "Loading",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel",
+ "common.open": "Open",
"common.connect": "Connect",
"common.disconnect": "Disconnect",
"common.submit": "Submit",
@@ -307,12 +310,17 @@ export const dict = {
"dialog.server.description": "Switch which OpenCode server this app connects to.",
"dialog.server.search.placeholder": "Search servers",
"dialog.server.empty": "No servers yet",
- "dialog.server.add.title": "Add a server",
- "dialog.server.add.url": "Server URL",
+ "dialog.server.add.title": "Add server",
+ "dialog.server.add.url": "Server address",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Could not connect to server",
"dialog.server.add.checking": "Checking...",
"dialog.server.add.button": "Add server",
+ "dialog.server.add.name": "Server name (optional)",
+ "dialog.server.add.namePlaceholder": "Localhost",
+ "dialog.server.add.username": "Username (optional)",
+ "dialog.server.add.password": "Password (optional)",
+ "dialog.server.edit.title": "Edit server",
"dialog.server.default.title": "Default server",
"dialog.server.default.description":
"Connect to this server on app launch instead of starting a local server. Requires restart.",
@@ -340,6 +348,11 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
+ "dialog.releaseNotes.action.getStarted": "Get started",
+ "dialog.releaseNotes.action.next": "Next",
+ "dialog.releaseNotes.action.hideFuture": "Don't show these in the future",
+ "dialog.releaseNotes.media.alt": "Release preview",
+
"context.breakdown.title": "Context Breakdown",
"context.breakdown.note": 'Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.',
"context.breakdown.system": "System",
@@ -390,6 +403,7 @@ export const dict = {
"language.br": "Português (Brasil)",
"language.bs": "Bosanski",
"language.th": "ไทย",
+ "language.tr": "Türkçe",
"toast.language.title": "Language",
"toast.language.description": "Switched to {{language}}",
@@ -402,10 +416,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",
@@ -428,6 +442,7 @@ export const dict = {
"toast.session.unshare.failed.description": "An error occurred while unsharing the session",
"toast.session.listFailed.title": "Failed to load sessions for {{project}}",
+ "toast.project.reloadFailed.title": "Failed to reload {{project}}",
"toast.update.title": "Update available",
"toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.",
@@ -452,6 +467,7 @@ export const dict = {
"directory.error.invalidUrl": "Invalid directory in URL.",
"error.chain.unknown": "Unknown error",
+ "error.server.invalidConfiguration": "Invalid configuration",
"error.chain.causedBy": "Caused by:",
"error.chain.apiError": "API error",
"error.chain.status": "Status: {{status}}",
@@ -495,10 +511,13 @@ export const dict = {
"session.review.change.other": "Changes",
"session.review.loadingChanges": "Loading changes...",
"session.review.empty": "No changes in this session yet",
+ "session.review.noVcs": "No Git Version Control System detected, changes not displayed",
+ "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable",
"session.review.noChanges": "No changes",
"session.files.selectToOpen": "Select a file to open",
"session.files.all": "All files",
+ "session.files.empty": "No files",
"session.files.binaryContent": "Binary file (content cannot be displayed)",
"session.messages.renderEarlier": "Render earlier messages",
@@ -512,6 +531,7 @@ export const dict = {
"session.todo.collapse": "Collapse",
"session.todo.expand": "Expand",
+ "session.new.title": "Build anything",
"session.new.worktree.main": "Main branch",
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
"session.new.worktree.create": "Create new worktree",
@@ -561,6 +581,7 @@ export const dict = {
"common.closeTab": "Close tab",
"common.dismiss": "Dismiss",
+ "common.moreCountSuffix": " (+{{count}} more)",
"common.requestFailed": "Request failed",
"common.moreOptions": "More options",
"common.learnMore": "Learn more",
@@ -573,6 +594,11 @@ export const dict = {
"common.loadMore": "Load more",
"common.key.esc": "ESC",
+ "common.time.justNow": "Just now",
+ "common.time.minutesAgo.short": "{{count}}m ago",
+ "common.time.hoursAgo.short": "{{count}}h ago",
+ "common.time.daysAgo.short": "{{count}}d ago",
+
"sidebar.menu.toggle": "Toggle menu",
"sidebar.nav.projectsAndSessions": "Projects and sessions",
"sidebar.settings": "Settings",
@@ -600,6 +626,7 @@ export const dict = {
"settings.general.section.notifications": "System notifications",
"settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Sound effects",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Display",
"settings.general.row.language.title": "Language",
@@ -612,6 +639,12 @@ export const dict = {
"settings.general.row.font.description": "Customise the mono font used in code blocks",
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
+ "settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Show shell tool parts expanded by default in the timeline",
+ "settings.general.row.editToolPartsExpanded.title": "Expand edit tool parts",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Show edit, write, and patch tool parts expanded by default in the timeline",
"settings.general.row.wayland.title": "Use native Wayland",
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
@@ -642,6 +675,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "None",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
@@ -725,7 +759,9 @@ export const dict = {
"settings.providers.description": "Provider settings will be configurable here.",
"settings.providers.section.connected": "Connected providers",
"settings.providers.connected.empty": "No connected providers",
+ "settings.providers.connected.environmentDescription": "Connected from your environment variables",
"settings.providers.section.popular": "Popular providers",
+ "settings.providers.custom.description": "Add an OpenAI-compatible provider by base URL.",
"settings.providers.tag.environment": "Environment",
"settings.providers.tag.config": "Config",
"settings.providers.tag.custom": "Custom",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index d741bb138b..77ef7970c4 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,8 +99,10 @@ 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 Claude para asistencia de codificación",
+ "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",
"dialog.provider.google.note": "Modelos Gemini para respuestas rápidas y estructuradas",
"dialog.provider.openrouter.note": "Accede a todos los modelos soportados desde un solo proveedor",
@@ -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",
@@ -514,6 +516,7 @@ export const dict = {
"session.todo.collapse": "Contraer",
"session.todo.expand": "Expandir",
+ "session.new.title": "Construye lo que quieras",
"session.new.worktree.main": "Rama principal",
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
"session.new.worktree.create": "Crear nuevo árbol de trabajo",
@@ -602,6 +605,7 @@ export const dict = {
"settings.general.section.notifications": "Notificaciones del sistema",
"settings.general.section.updates": "Actualizaciones",
"settings.general.section.sounds": "Efectos de sonido",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Pantalla",
"settings.general.row.language.title": "Idioma",
@@ -613,6 +617,12 @@ export const dict = {
"settings.general.row.font.title": "Fuente",
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
+ "settings.general.row.shellToolPartsExpanded.title": "Expandir partes de la herramienta shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Mostrar las partes de la herramienta shell expandidas por defecto en la línea de tiempo",
+ "settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
"settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
"settings.general.row.wayland.tooltip":
@@ -643,6 +653,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Ninguno",
"sound.option.alert01": "Alerta 01",
"sound.option.alert02": "Alerta 02",
"sound.option.alert03": "Alerta 03",
@@ -815,4 +826,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesión será archivada.",
"workspace.reset.archived.many": "{{count}} sesiones serán archivadas.",
"workspace.reset.note": "Esto restablecerá el espacio de trabajo para coincidir con la rama predeterminada.",
+ "common.open": "Abrir",
+ "dialog.releaseNotes.action.getStarted": "Comenzar",
+ "dialog.releaseNotes.action.next": "Siguiente",
+ "dialog.releaseNotes.action.hideFuture": "No mostrar esto en el futuro",
+ "dialog.releaseNotes.media.alt": "Vista previa de la versión",
+ "toast.project.reloadFailed.title": "Error al recargar {{project}}",
+ "error.server.invalidConfiguration": "Configuración inválida",
+ "common.moreCountSuffix": " (+{{count}} más)",
+ "common.time.justNow": "Justo ahora",
+ "common.time.minutesAgo.short": "hace {{count}} min",
+ "common.time.hoursAgo.short": "hace {{count}} h",
+ "common.time.daysAgo.short": "hace {{count}} d",
+ "settings.providers.connected.environmentDescription": "Conectado desde tus variables de entorno",
+ "settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.",
}
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index 686539df4d..c887f9ee8b 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",
@@ -463,6 +463,7 @@ export const dict = {
"session.todo.title": "Tâches",
"session.todo.collapse": "Réduire",
"session.todo.expand": "Développer",
+ "session.new.title": "Créez ce que vous voulez",
"session.new.worktree.main": "Branche principale",
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
"session.new.worktree.create": "Créer un nouvel arbre de travail",
@@ -543,6 +544,7 @@ export const dict = {
"settings.general.section.notifications": "Notifications système",
"settings.general.section.updates": "Mises à jour",
"settings.general.section.sounds": "Effets sonores",
+ "settings.general.section.feed": "Flux",
"settings.general.section.display": "Affichage",
"settings.general.row.language.title": "Langue",
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
@@ -552,6 +554,12 @@ export const dict = {
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
"settings.general.row.font.title": "Police",
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
+ "settings.general.row.shellToolPartsExpanded.title": "Développer les parties de l'outil shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Afficher les parties de l'outil shell développées par défaut dans la chronologie",
+ "settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
"settings.general.row.wayland.title": "Utiliser Wayland natif",
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
"settings.general.row.wayland.tooltip":
@@ -579,6 +587,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Aucun",
"sound.option.alert01": "Alerte 01",
"sound.option.alert02": "Alerte 02",
"sound.option.alert03": "Alerte 03",
@@ -741,4 +750,18 @@ export const dict = {
"workspace.reset.archived.one": "1 session sera archivée.",
"workspace.reset.archived.many": "{{count}} sessions seront archivées.",
"workspace.reset.note": "Cela réinitialisera l'espace de travail pour correspondre à la branche par défaut.",
+ "common.open": "Ouvrir",
+ "dialog.releaseNotes.action.getStarted": "Commencer",
+ "dialog.releaseNotes.action.next": "Suivant",
+ "dialog.releaseNotes.action.hideFuture": "Ne plus afficher à l'avenir",
+ "dialog.releaseNotes.media.alt": "Aperçu de la version",
+ "toast.project.reloadFailed.title": "Échec du rechargement de {{project}}",
+ "error.server.invalidConfiguration": "Configuration invalide",
+ "common.moreCountSuffix": " (+{{count}} de plus)",
+ "common.time.justNow": "À l'instant",
+ "common.time.minutesAgo.short": "il y a {{count}}m",
+ "common.time.hoursAgo.short": "il y a {{count}}h",
+ "common.time.daysAgo.short": "il y a {{count}}j",
+ "settings.providers.connected.environmentDescription": "Connecté à partir de vos variables d'environnement",
+ "settings.providers.custom.description": "Ajouter un fournisseur compatible avec OpenAI via l'URL de base.",
}
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index 288351c8be..9ddb6baf4a 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": "ファイルの読み込みに失敗しました",
@@ -455,6 +457,7 @@ export const dict = {
"session.todo.title": "ToDo",
"session.todo.collapse": "折りたたむ",
"session.todo.expand": "展開",
+ "session.new.title": "何でも作る",
"session.new.worktree.main": "メインブランチ",
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
"session.new.worktree.create": "新しいワークツリーを作成",
@@ -533,6 +536,7 @@ export const dict = {
"settings.general.section.notifications": "システム通知",
"settings.general.section.updates": "アップデート",
"settings.general.section.sounds": "効果音",
+ "settings.general.section.feed": "フィード",
"settings.general.section.display": "ディスプレイ",
"settings.general.row.language.title": "言語",
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
@@ -542,6 +546,12 @@ export const dict = {
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
"settings.general.row.font.title": "フォント",
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
+ "settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "タイムラインで shell ツールパーツをデフォルトで展開して表示します",
+ "settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
+ "settings.general.row.editToolPartsExpanded.description":
+ "タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
"settings.general.row.wayland.tooltip":
@@ -569,6 +579,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "なし",
"sound.option.alert01": "アラート 01",
"sound.option.alert02": "アラート 02",
"sound.option.alert03": "アラート 03",
@@ -728,4 +739,18 @@ export const dict = {
"workspace.reset.archived.one": "1つのセッションがアーカイブされます。",
"workspace.reset.archived.many": "{{count}}個のセッションがアーカイブされます。",
"workspace.reset.note": "これにより、ワークスペースはデフォルトブランチと一致するようにリセットされます。",
+ "common.open": "開く",
+ "dialog.releaseNotes.action.getStarted": "始める",
+ "dialog.releaseNotes.action.next": "次へ",
+ "dialog.releaseNotes.action.hideFuture": "今後表示しない",
+ "dialog.releaseNotes.media.alt": "リリースのプレビュー",
+ "toast.project.reloadFailed.title": "{{project}} の再読み込みに失敗しました",
+ "error.server.invalidConfiguration": "無効な設定",
+ "common.moreCountSuffix": " (他 {{count}} 件)",
+ "common.time.justNow": "たった今",
+ "common.time.minutesAgo.short": "{{count}} 分前",
+ "common.time.hoursAgo.short": "{{count}} 時間前",
+ "common.time.daysAgo.short": "{{count}} 日前",
+ "settings.providers.connected.environmentDescription": "環境変数から接続されました",
+ "settings.providers.custom.description": "ベース URL を指定して OpenAI 互換のプロバイダーを追加します。",
}
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index 72a46ca7e6..1e35106d1b 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": "파일 로드 실패",
@@ -457,6 +459,7 @@ export const dict = {
"session.todo.title": "할 일",
"session.todo.collapse": "접기",
"session.todo.expand": "펼치기",
+ "session.new.title": "무엇이든 만들기",
"session.new.worktree.main": "메인 브랜치",
"session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
"session.new.worktree.create": "새 작업 트리 생성",
@@ -534,6 +537,7 @@ export const dict = {
"settings.general.section.notifications": "시스템 알림",
"settings.general.section.updates": "업데이트",
"settings.general.section.sounds": "효과음",
+ "settings.general.section.feed": "피드",
"settings.general.section.display": "디스플레이",
"settings.general.row.language.title": "언어",
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
@@ -543,6 +547,12 @@ export const dict = {
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
"settings.general.row.font.title": "글꼴",
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
+ "settings.general.row.shellToolPartsExpanded.title": "shell 도구 파트 펼치기",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "타임라인에서 기본적으로 shell 도구 파트를 펼친 상태로 표시합니다",
+ "settings.general.row.editToolPartsExpanded.title": "edit 도구 파트 펼치기",
+ "settings.general.row.editToolPartsExpanded.description":
+ "타임라인에서 기본적으로 edit, write, patch 도구 파트를 펼친 상태로 표시합니다",
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
"settings.general.row.wayland.tooltip":
@@ -570,6 +580,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "없음",
"sound.option.alert01": "알림 01",
"sound.option.alert02": "알림 02",
"sound.option.alert03": "알림 03",
@@ -728,4 +739,18 @@ export const dict = {
"workspace.reset.archived.one": "1개의 세션이 보관됩니다.",
"workspace.reset.archived.many": "{{count}}개의 세션이 보관됩니다.",
"workspace.reset.note": "이 작업은 작업 공간을 기본 브랜치와 일치하도록 재설정합니다.",
+ "common.open": "열기",
+ "dialog.releaseNotes.action.getStarted": "시작하기",
+ "dialog.releaseNotes.action.next": "다음",
+ "dialog.releaseNotes.action.hideFuture": "다시 보지 않기",
+ "dialog.releaseNotes.media.alt": "릴리스 미리보기",
+ "toast.project.reloadFailed.title": "{{project}} 다시 불러오기 실패",
+ "error.server.invalidConfiguration": "잘못된 구성",
+ "common.moreCountSuffix": " (외 {{count}}개)",
+ "common.time.justNow": "방금 전",
+ "common.time.minutesAgo.short": "{{count}}분 전",
+ "common.time.hoursAgo.short": "{{count}}시간 전",
+ "common.time.daysAgo.short": "{{count}}일 전",
+ "settings.providers.connected.environmentDescription": "환경 변수에서 연결됨",
+ "settings.providers.custom.description": "기본 URL로 OpenAI 호환 공급자를 추가합니다.",
}
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index c099fe61f9..d9dac8ee55 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,8 +102,10 @@ 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": "Claude-modeller for kodeassistanse",
+ "dialog.provider.copilot.note": "AI-modeller for kodeassistanse via GitHub Copilot",
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
"dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar",
"dialog.provider.openrouter.note": "Tilgang til alle støttede modeller fra én leverandør",
@@ -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",
@@ -514,6 +516,7 @@ export const dict = {
"session.todo.collapse": "Skjul",
"session.todo.expand": "Utvid",
+ "session.new.title": "Bygg hva som helst",
"session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opprett nytt worktree",
@@ -602,6 +605,7 @@ export const dict = {
"settings.general.section.notifications": "Systemvarsler",
"settings.general.section.updates": "Oppdateringer",
"settings.general.section.sounds": "Lydeffekter",
+ "settings.general.section.feed": "Feed",
"settings.general.section.display": "Skjerm",
"settings.general.row.language.title": "Språk",
@@ -613,6 +617,11 @@ export const dict = {
"settings.general.row.font.title": "Skrift",
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
+ "settings.general.row.shellToolPartsExpanded.title": "Utvid shell-verktøydeler",
+ "settings.general.row.shellToolPartsExpanded.description": "Vis shell-verktøydeler utvidet som standard i tidslinjen",
+ "settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Vis edit-, write- og patch-verktøydeler utvidet som standard i tidslinjen",
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
"settings.general.row.wayland.tooltip":
@@ -642,6 +651,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Ingen",
"sound.option.alert01": "Varsel 01",
"sound.option.alert02": "Varsel 02",
"sound.option.alert03": "Varsel 03",
@@ -812,4 +822,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesjon vil bli arkivert.",
"workspace.reset.archived.many": "{{count}} sesjoner vil bli arkivert.",
"workspace.reset.note": "Dette vil tilbakestille arbeidsområdet til å samsvare med standardgrenen.",
+ "common.open": "Åpne",
+ "dialog.releaseNotes.action.getStarted": "Kom i gang",
+ "dialog.releaseNotes.action.next": "Neste",
+ "dialog.releaseNotes.action.hideFuture": "Ikke vis disse igjen",
+ "dialog.releaseNotes.media.alt": "Forhåndsvisning av utgivelse",
+ "toast.project.reloadFailed.title": "Kunne ikke laste inn {{project}} på nytt",
+ "error.server.invalidConfiguration": "Ugyldig konfigurasjon",
+ "common.moreCountSuffix": " (+{{count}} mer)",
+ "common.time.justNow": "Akkurat nå",
+ "common.time.minutesAgo.short": "{{count}} m siden",
+ "common.time.hoursAgo.short": "{{count}} t siden",
+ "common.time.daysAgo.short": "{{count}} d siden",
+ "settings.providers.connected.environmentDescription": "Koblet til fra miljøvariablene dine",
+ "settings.providers.custom.description": "Legg til en OpenAI-kompatibel leverandør via basis-URL.",
} satisfies Partial>
diff --git a/packages/app/src/i18n/parity.test.ts b/packages/app/src/i18n/parity.test.ts
index a75dbd3a30..c06a55ab17 100644
--- a/packages/app/src/i18n/parity.test.ts
+++ b/packages/app/src/i18n/parity.test.ts
@@ -15,8 +15,9 @@ import { dict as ru } from "./ru"
import { dict as th } from "./th"
import { dict as zh } from "./zh"
import { dict as zht } from "./zht"
+import { dict as tr } from "./tr"
-const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, zh, zht]
+const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, tr, zh, zht]
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const
describe("i18n parity", () => {
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 67c9dda2ac..b63fe5ee40 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,8 +91,10 @@ 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 Claude do pomocy w kodowaniu",
+ "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",
"dialog.provider.google.note": "Modele Gemini do szybkich i ustrukturyzowanych odpowiedzi",
"dialog.provider.openrouter.note": "Dostęp do wszystkich obsługiwanych modeli od jednego dostawcy",
@@ -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",
@@ -456,6 +458,7 @@ export const dict = {
"session.todo.title": "Zadania",
"session.todo.collapse": "Zwiń",
"session.todo.expand": "Rozwiń",
+ "session.new.title": "Zbuduj cokolwiek",
"session.new.worktree.main": "Główna gałąź",
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
@@ -534,6 +537,7 @@ export const dict = {
"settings.general.section.notifications": "Powiadomienia systemowe",
"settings.general.section.updates": "Aktualizacje",
"settings.general.section.sounds": "Efekty dźwiękowe",
+ "settings.general.section.feed": "Kanał",
"settings.general.section.display": "Ekran",
"settings.general.row.language.title": "Język",
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
@@ -543,6 +547,12 @@ export const dict = {
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
"settings.general.row.font.title": "Czcionka",
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
+ "settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Domyślnie pokazuj rozwinięte elementy narzędzia shell na osi czasu",
+ "settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
"settings.general.row.wayland.tooltip":
@@ -570,6 +580,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Brak",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
@@ -730,4 +741,18 @@ export const dict = {
"workspace.reset.archived.one": "1 sesja zostanie zarchiwizowana.",
"workspace.reset.archived.many": "{{count}} sesji zostanie zarchiwizowanych.",
"workspace.reset.note": "To zresetuje przestrzeń roboczą, aby odpowiadała domyślnej gałęzi.",
+ "common.open": "Otwórz",
+ "dialog.releaseNotes.action.getStarted": "Rozpocznij",
+ "dialog.releaseNotes.action.next": "Dalej",
+ "dialog.releaseNotes.action.hideFuture": "Nie pokazuj tego w przyszłości",
+ "dialog.releaseNotes.media.alt": "Podgląd wydania",
+ "toast.project.reloadFailed.title": "Nie udało się ponownie wczytać {{project}}",
+ "error.server.invalidConfiguration": "Nieprawidłowa konfiguracja",
+ "common.moreCountSuffix": " (jeszcze {{count}})",
+ "common.time.justNow": "Przed chwilą",
+ "common.time.minutesAgo.short": "{{count}} min temu",
+ "common.time.hoursAgo.short": "{{count}} godz. temu",
+ "common.time.daysAgo.short": "{{count}} dni temu",
+ "settings.providers.connected.environmentDescription": "Połączono ze zmiennymi środowiskowymi",
+ "settings.providers.custom.description": "Dodaj dostawcę zgodnego z OpenAI poprzez podstawowy URL.",
}
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index 57ef82fd66..aadb926d27 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,8 +99,10 @@ 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": "Модели Claude для помощи в кодировании",
+ "dialog.provider.copilot.note": "ИИ-модели для помощи в кодировании через GitHub Copilot",
"dialog.provider.openai.note": "Модели GPT для быстрых и мощных задач общего ИИ",
"dialog.provider.google.note": "Модели Gemini для быстрых и структурированных ответов",
"dialog.provider.openrouter.note": "Доступ ко всем поддерживаемым моделям через одного провайдера",
@@ -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": "В боковой панели теперь отображаются несколько рабочих деревьев",
@@ -512,6 +514,7 @@ export const dict = {
"session.todo.collapse": "Свернуть",
"session.todo.expand": "Развернуть",
+ "session.new.title": "Создавайте что угодно",
"session.new.worktree.main": "Основная ветка",
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
"session.new.worktree.create": "Создать новый worktree",
@@ -600,6 +603,7 @@ export const dict = {
"settings.general.section.notifications": "Системные уведомления",
"settings.general.section.updates": "Обновления",
"settings.general.section.sounds": "Звуковые эффекты",
+ "settings.general.section.feed": "Лента",
"settings.general.section.display": "Дисплей",
"settings.general.row.language.title": "Язык",
@@ -611,6 +615,12 @@ export const dict = {
"settings.general.row.font.title": "Шрифт",
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
+ "settings.general.row.shellToolPartsExpanded.title": "Разворачивать элементы инструмента shell",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Показывать элементы инструмента shell в ленте развернутыми по умолчанию",
+ "settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
"settings.general.row.wayland.title": "Использовать нативный Wayland",
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
"settings.general.row.wayland.tooltip":
@@ -640,6 +650,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "Нет",
"sound.option.alert01": "Alert 01",
"sound.option.alert02": "Alert 02",
"sound.option.alert03": "Alert 03",
@@ -811,4 +822,18 @@ export const dict = {
"workspace.reset.archived.one": "1 сессия будет архивирована.",
"workspace.reset.archived.many": "{{count}} сессий будет архивировано.",
"workspace.reset.note": "Рабочее пространство будет сброшено в соответствие с веткой по умолчанию.",
+ "common.open": "Открыть",
+ "dialog.releaseNotes.action.getStarted": "Начать",
+ "dialog.releaseNotes.action.next": "Далее",
+ "dialog.releaseNotes.action.hideFuture": "Больше не показывать",
+ "dialog.releaseNotes.media.alt": "Превью релиза",
+ "toast.project.reloadFailed.title": "Не удалось перезагрузить {{project}}",
+ "error.server.invalidConfiguration": "Недопустимая конфигурация",
+ "common.moreCountSuffix": " (ещё {{count}})",
+ "common.time.justNow": "Только что",
+ "common.time.minutesAgo.short": "{{count}} мин назад",
+ "common.time.hoursAgo.short": "{{count}} ч назад",
+ "common.time.daysAgo.short": "{{count}} д назад",
+ "settings.providers.connected.environmentDescription": "Подключено из ваших переменных окружения",
+ "settings.providers.custom.description": "Добавить провайдера, совместимого с OpenAI, по базовому URL.",
}
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index e67db04651..6a25a356a9 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,8 +99,10 @@ 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": "โมเดล Claude สำหรับการช่วยเหลือในการเขียนโค้ด",
+ "dialog.provider.copilot.note": "โมเดล AI สำหรับการช่วยเหลือในการเขียนโค้ดผ่าน GitHub Copilot",
"dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ",
"dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง",
"dialog.provider.openrouter.note": "เข้าถึงโมเดลที่รองรับทั้งหมดจากผู้ให้บริการเดียว",
@@ -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": "เชื่อมต่อผู้ให้บริการเพื่อสรุปเซสชันนี้",
@@ -509,6 +511,7 @@ export const dict = {
"session.todo.collapse": "ย่อ",
"session.todo.expand": "ขยาย",
+ "session.new.title": "สร้างอะไรก็ได้",
"session.new.worktree.main": "สาขาหลัก",
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
"session.new.worktree.create": "สร้าง worktree ใหม่",
@@ -594,6 +597,7 @@ export const dict = {
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
"settings.general.section.updates": "การอัปเดต",
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
+ "settings.general.section.feed": "ฟีด",
"settings.general.section.display": "การแสดงผล",
"settings.general.row.language.title": "ภาษา",
@@ -605,6 +609,11 @@ export const dict = {
"settings.general.row.font.title": "ฟอนต์",
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
+ "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
+ "settings.general.row.shellToolPartsExpanded.description": "แสดงส่วนเครื่องมือ shell แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
+ "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
+ "settings.general.row.editToolPartsExpanded.description":
+ "แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
@@ -634,6 +643,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "ไม่มี",
"sound.option.alert01": "เสียงเตือน 01",
"sound.option.alert02": "เสียงเตือน 02",
"sound.option.alert03": "เสียงเตือน 03",
@@ -802,4 +812,18 @@ export const dict = {
"workspace.reset.archived.one": "1 เซสชันจะถูกจัดเก็บ",
"workspace.reset.archived.many": "{{count}} เซสชันจะถูกจัดเก็บ",
"workspace.reset.note": "สิ่งนี้จะรีเซ็ตพื้นที่ทำงานให้ตรงกับสาขาเริ่มต้น",
+ "common.open": "เปิด",
+ "dialog.releaseNotes.action.getStarted": "เริ่มต้น",
+ "dialog.releaseNotes.action.next": "ถัดไป",
+ "dialog.releaseNotes.action.hideFuture": "ไม่ต้องแสดงสิ่งนี้อีกในอนาคต",
+ "dialog.releaseNotes.media.alt": "ตัวอย่างรุ่น",
+ "toast.project.reloadFailed.title": "ไม่สามารถโหลด {{project}} ใหม่ได้",
+ "error.server.invalidConfiguration": "การกำหนดค่าไม่ถูกต้อง",
+ "common.moreCountSuffix": " (เพิ่มอีก {{count}})",
+ "common.time.justNow": "เมื่อสักครู่นี้",
+ "common.time.minutesAgo.short": "{{count}} นาทีที่แล้ว",
+ "common.time.hoursAgo.short": "{{count}} ชม. ที่แล้ว",
+ "common.time.daysAgo.short": "{{count}} วันที่แล้ว",
+ "settings.providers.connected.environmentDescription": "เชื่อมต่อจากตัวแปรสภาพแวดล้อมของคุณ",
+ "settings.providers.custom.description": "เพิ่มผู้ให้บริการที่รองรับ OpenAI ด้วย URL หลัก",
}
diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts
new file mode 100644
index 0000000000..50e5598324
--- /dev/null
+++ b/packages/app/src/i18n/tr.ts
@@ -0,0 +1,850 @@
+import { dict as en } from "./en"
+
+type Keys = keyof typeof en
+
+export const dict = {
+ "command.category.suggested": "Önerilen",
+ "command.category.view": "Görünüm",
+ "command.category.project": "Proje",
+ "command.category.provider": "Sağlayıcı",
+ "command.category.server": "Sunucu",
+ "command.category.session": "Oturum",
+ "command.category.theme": "Tema",
+ "command.category.language": "Dil",
+ "command.category.file": "Dosya",
+ "command.category.context": "Bağlam",
+ "command.category.terminal": "Terminal",
+ "command.category.model": "Model",
+ "command.category.mcp": "MCP",
+ "command.category.agent": "Ajan",
+ "command.category.permissions": "İzinler",
+ "command.category.workspace": "Çalışma Alanı",
+ "command.category.settings": "Ayarlar",
+
+ "theme.scheme.system": "Sistem",
+ "theme.scheme.light": "Açık",
+ "theme.scheme.dark": "Koyu",
+
+ "command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
+ "command.project.open": "Proje aç",
+ "command.provider.connect": "Sağlayıcı bağla",
+ "command.server.switch": "Sunucu değiştir",
+ "command.settings.open": "Ayarları aç",
+ "command.session.previous": "Önceki oturum",
+ "command.session.next": "Sonraki oturum",
+ "command.session.previous.unseen": "Önceki okunmamış oturum",
+ "command.session.next.unseen": "Sonraki okunmamış oturum",
+ "command.session.archive": "Oturumu arşivle",
+
+ "command.palette": "Komut paleti",
+
+ "command.theme.cycle": "Tema değiştir",
+ "command.theme.set": "Tema kullan: {{theme}}",
+ "command.theme.scheme.cycle": "Renk şemasını değiştir",
+ "command.theme.scheme.set": "Renk şeması kullan: {{scheme}}",
+
+ "command.language.cycle": "Dil değiştir",
+ "command.language.set": "Dil kullan: {{language}}",
+
+ "command.session.new": "Yeni oturum",
+ "command.file.open": "Dosya aç",
+ "command.tab.close": "Sekmeyi kapat",
+ "command.context.addSelection": "Seçimi bağlama ekle",
+ "command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
+ "command.input.focus": "Girişi odakla",
+ "command.terminal.toggle": "Terminali aç/kapat",
+ "command.fileTree.toggle": "Dosya ağacını aç/kapat",
+ "command.review.toggle": "İncelemeyi aç/kapat",
+ "command.terminal.new": "Yeni terminal",
+ "command.terminal.new.description": "Yeni bir terminal sekmesi oluştur",
+ "command.steps.toggle": "Adımları aç/kapat",
+ "command.steps.toggle.description": "Mevcut mesaj için adımları göster veya gizle",
+ "command.message.previous": "Önceki mesaj",
+ "command.message.previous.description": "Önceki kullanıcı mesajına git",
+ "command.message.next": "Sonraki mesaj",
+ "command.message.next.description": "Sonraki kullanıcı mesajına git",
+ "command.model.choose": "Model seç",
+ "command.model.choose.description": "Farklı bir model seç",
+ "command.mcp.toggle": "MCP'leri aç/kapat",
+ "command.mcp.toggle.description": "MCP'leri aç/kapat",
+ "command.agent.cycle": "Ajan değiştir",
+ "command.agent.cycle.description": "Sonraki ajana geç",
+ "command.agent.cycle.reverse": "Ajanı geri değiştir",
+ "command.agent.cycle.reverse.description": "Önceki ajana geç",
+ "command.model.variant.cycle": "Düşünme eforu değiştir",
+ "command.model.variant.cycle.description": "Sonraki efor seviyesine geç",
+ "command.prompt.mode.shell": "Kabuk",
+ "command.prompt.mode.normal": "Komut",
+ "command.permissions.autoaccept.enable": "Düzenlemeleri otomatik kabul et",
+ "command.permissions.autoaccept.disable": "Otomatik kabulü durdur",
+ "command.workspace.toggle": "Çalışma alanlarını aç/kapat",
+ "command.workspace.toggle.description": "Kenar çubuğunda birden fazla çalışma alanını göster veya gizle",
+ "command.session.undo": "Geri al",
+ "command.session.undo.description": "Son mesajı geri al",
+ "command.session.redo": "Yinele",
+ "command.session.redo.description": "Son geri alınan mesajı yinele",
+ "command.session.compact": "Oturumu sıkıştır",
+ "command.session.compact.description": "Bağlam boyutunu azaltmak için oturumu özetle",
+ "command.session.fork": "Mesajdan dallandır",
+ "command.session.fork.description": "Önceki bir mesajdan yeni oturum oluştur",
+ "command.session.share": "Oturumu paylaş",
+ "command.session.share.description": "Bu oturumu paylaş ve URL'yi panoya kopyala",
+ "command.session.unshare": "Paylaşımı kaldır",
+ "command.session.unshare.description": "Bu oturumun paylaşımını durdur",
+
+ "palette.search.placeholder": "Dosya, komut ve oturum ara",
+ "palette.empty": "Sonuç bulunamadı",
+ "palette.group.commands": "Komutlar",
+ "palette.group.files": "Dosyalar",
+
+ "dialog.provider.search.placeholder": "Sağlayıcı ara",
+ "dialog.provider.empty": "Sağlayıcı bulunamadı",
+ "dialog.provider.group.popular": "Popüler",
+ "dialog.provider.group.other": "Diğer",
+ "dialog.provider.tag.recommended": "Önerilen",
+ "dialog.provider.opencode.note": "Claude, GPT, Gemini ve daha fazlasını içeren seçilmiş modeller",
+ "dialog.provider.opencode.tagline": "Güvenilir optimize edilmiş modeller",
+ "dialog.provider.opencodeGo.tagline": "Herkes için düşük maliyetli abonelik",
+ "dialog.provider.anthropic.note": "Pro ve Max dahil Claude modellerine doğrudan erişim",
+ "dialog.provider.copilot.note": "GitHub Copilot üzerinden kodlama yardımı için yapay zekâ modelleri",
+ "dialog.provider.openai.note": "Hızlı ve yetenekli genel yapay zekâ görevleri için GPT modelleri",
+ "dialog.provider.google.note": "Hızlı ve yapılandırılmış yanıtlar için Gemini modelleri",
+ "dialog.provider.openrouter.note": "Tek bir sağlayıcıdan tüm desteklenen modellere eriş",
+ "dialog.provider.vercel.note": "Akıllı yönlendirme ile yapay zekâ modellerine birleşik erişim",
+
+ "dialog.model.select.title": "Model seç",
+ "dialog.model.search.placeholder": "Model ara",
+ "dialog.model.empty": "Model sonucu yok",
+ "dialog.model.manage": "Modelleri yönet",
+ "dialog.model.manage.description": "Model seçicide hangi modellerin görüneceğini özelleştirin.",
+ "dialog.model.manage.provider.toggle": "Tüm {{provider}} modellerini aç/kapat",
+
+ "dialog.model.unpaid.freeModels.title": "OpenCode tarafından sunulan ücretsiz modeller",
+ "dialog.model.unpaid.addMore.title": "Popüler sağlayıcılardan daha fazla model ekleyin",
+
+ "dialog.provider.viewAll": "Daha fazla sağlayıcı göster",
+
+ "provider.connect.title": "{{provider}} bağla",
+ "provider.connect.title.anthropicProMax": "Claude Pro/Max ile giriş yap",
+ "provider.connect.selectMethod": "{{provider}} için giriş yöntemini seçin.",
+ "provider.connect.method.apiKey": "API anahtarı",
+ "provider.connect.status.inProgress": "Yetkilendirme devam ediyor...",
+ "provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
+ "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
+ "provider.connect.apiKey.description":
+ "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.",
+ "provider.connect.apiKey.label": "{{provider}} API anahtarı",
+ "provider.connect.apiKey.placeholder": "API anahtarı",
+ "provider.connect.apiKey.required": "API anahtarı gerekli",
+ "provider.connect.opencodeZen.line1":
+ "OpenCode Zen, kodlama ajanları için seçilmiş güvenilir optimize edilmiş modellere erişim sağlar.",
+ "provider.connect.opencodeZen.line2":
+ "Tek bir API anahtarıyla Claude, GPT, Gemini, GLM ve daha fazlası gibi modellere erişebilirsiniz.",
+ "provider.connect.opencodeZen.visit.prefix": "",
+ "provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
+ "provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
+ "provider.connect.oauth.code.visit.prefix":
+ "Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
+ "provider.connect.oauth.code.visit.link": "bu bağlantıya",
+ "provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.",
+ "provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
+ "provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
+ "provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
+ "provider.connect.oauth.code.invalid": "Geçersiz yetkilendirme kodu",
+ "provider.connect.oauth.auto.visit.prefix": "",
+ "provider.connect.oauth.auto.visit.link": "Bu bağlantıya",
+ "provider.connect.oauth.auto.visit.suffix":
+ " tıklayarak aşağıdaki kodu girin ve hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.",
+ "provider.connect.oauth.auto.confirmationCode": "Onay kodu",
+ "provider.connect.toast.connected.title": "{{provider}} bağlandı",
+ "provider.connect.toast.connected.description": "{{provider}} modelleri artık kullanımda.",
+
+ "provider.custom.title": "Özel sağlayıcı",
+ "provider.custom.description.prefix": "OpenAI uyumlu bir sağlayıcı yapılandırın. ",
+ "provider.custom.description.link": "Sağlayıcı yapılandırma dökümanları",
+ "provider.custom.description.suffix": " sayfasına bakın.",
+ "provider.custom.field.providerID.label": "Sağlayıcı kimlik",
+ "provider.custom.field.providerID.placeholder": "saglayicim",
+ "provider.custom.field.providerID.description": "Küçük harfler, rakamlar, tire veya alt çizgi",
+ "provider.custom.field.name.label": "Görünen ad",
+ "provider.custom.field.name.placeholder": "Yapay Zekâ Sağlayıcım",
+ "provider.custom.field.baseURL.label": "Temel URL",
+ "provider.custom.field.baseURL.placeholder": "https://api.saglayicim.com/v1",
+ "provider.custom.field.apiKey.label": "API anahtarı",
+ "provider.custom.field.apiKey.placeholder": "API anahtarı",
+ "provider.custom.field.apiKey.description":
+ "İsteğe bağlı. Kimlik doğrulamayı başlıklar ile yönetiyorsanız boş bırakın.",
+ "provider.custom.models.label": "Modeller",
+ "provider.custom.models.id.label": "Kimlik",
+ "provider.custom.models.id.placeholder": "model-kimlik",
+ "provider.custom.models.name.label": "Ad",
+ "provider.custom.models.name.placeholder": "Görünen Ad",
+ "provider.custom.models.remove": "Modeli kaldır",
+ "provider.custom.models.add": "Model ekle",
+ "provider.custom.headers.label": "Başlıklar (isteğe bağlı)",
+ "provider.custom.headers.key.label": "Başlık",
+ "provider.custom.headers.key.placeholder": "Başlık-Adı",
+ "provider.custom.headers.value.label": "Değer",
+ "provider.custom.headers.value.placeholder": "değer",
+ "provider.custom.headers.remove": "Başlığı kaldır",
+ "provider.custom.headers.add": "Başlık ekle",
+ "provider.custom.error.providerID.required": "Sağlayıcı kimlik gerekli",
+ "provider.custom.error.providerID.format": "Küçük harf, rakam, tire veya alt çizgi kullanın",
+ "provider.custom.error.providerID.exists": "Bu sağlayıcı kimlik zaten mevcut",
+ "provider.custom.error.name.required": "Görünen ad gerekli",
+ "provider.custom.error.baseURL.required": "Temel URL gerekli",
+ "provider.custom.error.baseURL.format": "http:// veya https:// ile başlamalı",
+ "provider.custom.error.required": "Gerekli",
+ "provider.custom.error.duplicate": "Tekrar",
+
+ "provider.disconnect.toast.disconnected.title": "{{provider}} bağlantısı kesildi",
+ "provider.disconnect.toast.disconnected.description": "{{provider}} modelleri artık kullanılabilir değil.",
+
+ "model.tag.free": "Ücretsiz",
+ "model.tag.latest": "En yeni",
+ "model.provider.anthropic": "Anthropic",
+ "model.provider.openai": "OpenAI",
+ "model.provider.google": "Google",
+ "model.provider.xai": "xAI",
+ "model.provider.meta": "Meta",
+ "model.input.text": "metin",
+ "model.input.image": "görsel",
+ "model.input.audio": "ses",
+ "model.input.video": "video",
+ "model.input.pdf": "pdf",
+ "model.tooltip.allows": "Kabul eder: {{inputs}}",
+ "model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
+ "model.tooltip.reasoning.none": "Akıl yürütme yok",
+ "model.tooltip.context": "Bağlam limiti {{limit}}",
+
+ "common.search.placeholder": "Ara",
+ "common.goBack": "Geri git",
+ "common.goForward": "İleri git",
+ "common.loading": "Yükleniyor",
+ "common.loading.ellipsis": "...",
+ "common.cancel": "İptal",
+ "common.connect": "Bağlan",
+ "common.disconnect": "Bağlantı Kes",
+ "common.submit": "Gönder",
+ "common.save": "Kaydet",
+ "common.saving": "Kaydediliyor...",
+ "common.default": "Varsayılan",
+ "common.attachment": "ek",
+
+ "prompt.placeholder.shell": "Kabuk komutu girin...",
+ "prompt.placeholder.normal": 'Bir şeyler sorun... "{{example}}"',
+ "prompt.placeholder.simple": "Bir şeyler sorun...",
+ "prompt.placeholder.summarizeComments": "Yorumları özetle…",
+ "prompt.placeholder.summarizeComment": "Yorumu özetle…",
+ "prompt.mode.shell": "Kabuk",
+ "prompt.mode.normal": "Komut",
+ "prompt.mode.shell.exit": "çıkmak için esc",
+
+ "prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
+ "prompt.example.2": "Bu projenin teknoloji yığını nedir?",
+ "prompt.example.3": "Bozuk testleri düzelt",
+ "prompt.example.4": "Kimlik doğrulamanın nasıl çalıştığını açıkla",
+ "prompt.example.5": "Güvenlik açıkları bul ve düzelt",
+ "prompt.example.6": "Kullanıcı servisi için birim testleri ekle",
+ "prompt.example.7": "Bu fonksiyonu daha okunabilir hale getir",
+ "prompt.example.8": "Bu hata ne anlama geliyor?",
+ "prompt.example.9": "Bu sorunu ayıklamama yardım et",
+ "prompt.example.10": "API dokümantasyonu oluştur",
+ "prompt.example.11": "Veritabanı sorgularını optimize et",
+ "prompt.example.12": "Girdi doğrulama ekle",
+ "prompt.example.13": "İçin yeni bir bileşen oluştur...",
+ "prompt.example.14": "Bu projeyi nasıl dağıtabilirim?",
+ "prompt.example.15": "Kodumu en iyi uygulamalar için incele",
+ "prompt.example.16": "Bu fonksiyona hata yönetimi ekle",
+ "prompt.example.17": "Bu regex kalıbını açıkla",
+ "prompt.example.18": "Bunu TypeScript'e dönüştür",
+ "prompt.example.19": "Kod tabanı boyunca loglama ekle",
+ "prompt.example.20": "Hangi bağımlılıklar güncellenmemiş?",
+ "prompt.example.21": "Bir göç betiği yazmama yardım et",
+ "prompt.example.22": "Bu uç nokta için önbellekleme uygula",
+ "prompt.example.23": "Bu listeye sayfalama ekle",
+ "prompt.example.24": "İçin bir CLI komutu oluştur...",
+ "prompt.example.25": "Ortam değişkenleri burada nasıl çalışıyor?",
+
+ "prompt.popover.emptyResults": "Eşleşen sonuç yok",
+ "prompt.popover.emptyCommands": "Eşleşen komut yok",
+ "prompt.dropzone.label": "Görsel veya PDF'leri buraya bırakın",
+ "prompt.dropzone.file.label": "@bahsetmek için dosyayı bırakın",
+ "prompt.slash.badge.custom": "özel",
+ "prompt.slash.badge.skill": "beceri",
+ "prompt.slash.badge.mcp": "mcp",
+ "prompt.context.active": "aktif",
+ "prompt.context.includeActiveFile": "Aktif dosyayı dahil et",
+ "prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
+ "prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
+ "prompt.action.attachFile": "Dosya ekle",
+ "prompt.attachment.remove": "Eki kaldır",
+ "prompt.action.send": "Gönder",
+ "prompt.action.stop": "Durdur",
+
+ "prompt.toast.pasteUnsupported.title": "Desteklenmeyen yapıştırma",
+ "prompt.toast.pasteUnsupported.description": "Buraya sadece görsel veya PDF yapıştırılabilir.",
+ "prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin",
+ "prompt.toast.modelAgentRequired.description": "Komut göndermeden önce bir ajan ve model seçin.",
+ "prompt.toast.worktreeCreateFailed.title": "Çalışma ağacı oluşturulamadı",
+ "prompt.toast.sessionCreateFailed.title": "Oturum oluşturulamadı",
+ "prompt.toast.shellSendFailed.title": "Kabuk komutu gönderilemedi",
+ "prompt.toast.commandSendFailed.title": "Komut gönderilemedi",
+ "prompt.toast.promptSendFailed.title": "Komut gönderilemedi",
+ "prompt.toast.promptSendFailed.description": "Oturum alınamadı",
+
+ "dialog.mcp.title": "MCP'ler",
+ "dialog.mcp.description": "{{total}} içerisinden {{enabled}} etkin",
+ "dialog.mcp.empty": "Yapılandırılmış MCP yok",
+
+ "dialog.lsp.empty": "LSP'ler dosya türlerinden otomatik algılanır",
+ "dialog.plugins.empty": "Eklentiler opencode.json içinde yapılandırılır",
+
+ "mcp.status.connected": "bağlı",
+ "mcp.status.failed": "başarısız",
+ "mcp.status.needs_auth": "kimlik doğrulama gerekli",
+ "mcp.status.disabled": "devre dışı",
+
+ "dialog.fork.empty": "Dallandırılacak mesaj yok",
+
+ "dialog.directory.search.placeholder": "Klasör ara",
+ "dialog.directory.empty": "Klasör bulunamadı",
+
+ "dialog.server.title": "Sunucular",
+ "dialog.server.description": "Bu uygulamanın hangi OpenCode sunucusuna bağlanacağını değiştirin.",
+ "dialog.server.search.placeholder": "Sunucu ara",
+ "dialog.server.empty": "Henüz sunucu yok",
+ "dialog.server.add.title": "Sunucu ekle",
+ "dialog.server.add.url": "Sunucu URL'si",
+ "dialog.server.add.placeholder": "http://localhost:4096",
+ "dialog.server.add.error": "Sunucuya bağlanılamadı",
+ "dialog.server.add.checking": "Kontrol ediliyor...",
+ "dialog.server.add.button": "Sunucu ekle",
+ "dialog.server.default.title": "Varsayılan sunucu",
+ "dialog.server.default.description":
+ "Uygulama başlatıldığında yerel sunucu başlatmak yerine bu sunucuya bağlan. Yeniden başlatma gerektirir.",
+ "dialog.server.default.none": "Sunucu seçilmedi",
+ "dialog.server.default.set": "Mevcut sunucuyu varsayılan olarak ayarla",
+ "dialog.server.default.clear": "Temizle",
+ "dialog.server.action.remove": "Sunucuyu kaldır",
+
+ "dialog.server.menu.edit": "Düzenle",
+ "dialog.server.menu.default": "Varsayılan olarak ayarla",
+ "dialog.server.menu.defaultRemove": "Varsayılanı kaldır",
+ "dialog.server.menu.delete": "Sil",
+ "dialog.server.current": "Mevcut Sunucu",
+ "dialog.server.status.default": "Varsayılan",
+
+ "dialog.project.edit.title": "Projeyi düzenle",
+ "dialog.project.edit.name": "Ad",
+ "dialog.project.edit.icon": "Simge",
+ "dialog.project.edit.icon.alt": "Proje simgesi",
+ "dialog.project.edit.icon.hint": "Tıkla veya bir görsel sürükle",
+ "dialog.project.edit.icon.recommended": "Önerilen: 128x128px",
+ "dialog.project.edit.color": "Renk",
+ "dialog.project.edit.color.select": "{{color}} rengini seç",
+ "dialog.project.edit.worktree.startup": "Çalışma alanı başlatma betiği",
+ "dialog.project.edit.worktree.startup.description": "Yeni bir çalışma alanı (worktree) oluşturduktan sonra çalışır.",
+ "dialog.project.edit.worktree.startup.placeholder": "örneğin bun install",
+
+ "context.breakdown.title": "Bağlam Dökümü",
+ "context.breakdown.note": 'Girdi tokenlerinin yaklaşık dökümü. "Diğer" araç tanımları ve ek yükleri içerir.',
+ "context.breakdown.system": "Sistem",
+ "context.breakdown.user": "Kullanıcı",
+ "context.breakdown.assistant": "Asistan",
+ "context.breakdown.tool": "Araç Çağrıları",
+ "context.breakdown.other": "Diğer",
+
+ "context.systemPrompt.title": "Sistem Komutu",
+ "context.rawMessages.title": "Ham mesajlar",
+
+ "context.stats.session": "Oturum",
+ "context.stats.messages": "Mesajlar",
+ "context.stats.provider": "Sağlayıcı",
+ "context.stats.model": "Model",
+ "context.stats.limit": "Bağlam Limiti",
+ "context.stats.totalTokens": "Toplam Token",
+ "context.stats.usage": "Kullanım",
+ "context.stats.inputTokens": "Girdi Tokenleri",
+ "context.stats.outputTokens": "Çıktı Tokenleri",
+ "context.stats.reasoningTokens": "Akıl Yürütme Tokenleri",
+ "context.stats.cacheTokens": "Önbellek Tokenleri (okuma/yazma)",
+ "context.stats.userMessages": "Kullanıcı Mesajları",
+ "context.stats.assistantMessages": "Asistan Mesajları",
+ "context.stats.totalCost": "Toplam Maliyet",
+ "context.stats.sessionCreated": "Oturum Oluşturulma",
+ "context.stats.lastActivity": "Son Etkinlik",
+
+ "context.usage.tokens": "Tokenler",
+ "context.usage.usage": "Kullanım",
+ "context.usage.cost": "Maliyet",
+ "context.usage.clickToView": "Bağlamı görüntüle",
+ "context.usage.view": "Bağlam kullanımını görüntüle",
+
+ "language.en": "English",
+ "language.zh": "简体中文",
+ "language.zht": "繁體中文",
+ "language.ko": "한국어",
+ "language.de": "Deutsch",
+ "language.es": "Español",
+ "language.fr": "Français",
+ "language.da": "Dansk",
+ "language.ja": "日本語",
+ "language.pl": "Polski",
+ "language.ru": "Русский",
+ "language.ar": "العربية",
+ "language.no": "Norsk",
+ "language.br": "Português (Brasil)",
+ "language.bs": "Bosanski",
+ "language.th": "ไทย",
+ "language.tr": "Türkçe",
+
+ "toast.language.title": "Dil",
+ "toast.language.description": "{{language}} diline geçildi",
+
+ "toast.theme.title": "Tema değiştirildi",
+ "toast.scheme.title": "Renk şeması",
+
+ "toast.workspace.enabled.title": "Çalışma alanları etkinleştirildi",
+ "toast.workspace.enabled.description": "Kenar çubuğunda birden fazla çalışma ağacı gösterilecek",
+ "toast.workspace.disabled.title": "Çalışma alanları devre dışı bırakıldı",
+ "toast.workspace.disabled.description": "Kenar çubuğunda yalnızca ana çalışma ağacı gösterilecek",
+
+ "toast.permissions.autoaccept.on.title": "Düzenlemeler otomatik kabul ediliyor",
+ "toast.permissions.autoaccept.on.description": "Düzenleme ve yazma izinleri otomatik olarak onaylanacak",
+ "toast.permissions.autoaccept.off.title": "Otomatik kabul durduruldu",
+ "toast.permissions.autoaccept.off.description": "Düzenleme ve yazma izinleri onay gerektirecek",
+
+ "toast.model.none.title": "Model seçilmedi",
+ "toast.model.none.description": "Bu oturumu özetlemek için bir sağlayıcı bağlayın",
+
+ "toast.file.loadFailed.title": "Dosya yüklenemedi",
+ "toast.file.listFailed.title": "Dosyalar listelenemedi",
+
+ "toast.context.noLineSelection.title": "Satır seçimi yok",
+ "toast.context.noLineSelection.description": "Önce bir dosya sekmesinde satır aralığı seçin.",
+
+ "toast.session.share.copyFailed.title": "URL panoya kopyalanamadı",
+ "toast.session.share.success.title": "Oturum paylaşıldı",
+ "toast.session.share.success.description": "Paylaşım URL'si panoya kopyalandı!",
+ "toast.session.share.failed.title": "Oturum paylaşılamadı",
+ "toast.session.share.failed.description": "Oturum paylaşılırken bir hata oluştu",
+
+ "toast.session.unshare.success.title": "Oturum paylaşımı kaldırıldı",
+ "toast.session.unshare.success.description": "Oturum paylaşımı başarıyla kaldırıldı!",
+ "toast.session.unshare.failed.title": "Oturum paylaşımı kaldırılamadı",
+ "toast.session.unshare.failed.description": "Oturum paylaşımı kaldırılırken bir hata oluştu",
+
+ "toast.session.listFailed.title": "{{project}} için oturumlar yüklenemedi",
+
+ "toast.update.title": "Güncelleme mevcut",
+ "toast.update.description": "OpenCode'un yeni bir sürümü ({{version}}) yüklemeye hazır.",
+ "toast.update.action.installRestart": "Yükle ve yeniden başlat",
+ "toast.update.action.notYet": "Şimdi değil",
+
+ "error.page.title": "Bir şeyler yanlış gitti",
+ "error.page.description": "Uygulama yüklenirken bir hata oluştu.",
+ "error.page.details.label": "Hata Detayları",
+ "error.page.action.restart": "Yeniden Başlat",
+ "error.page.action.checking": "Kontrol ediliyor...",
+ "error.page.action.checkUpdates": "Güncellemeleri kontrol et",
+ "error.page.action.updateTo": "{{version}} sürümüne güncelle",
+ "error.page.report.prefix": "Lütfen bu hatayı OpenCode ekibine bildirin",
+ "error.page.report.discord": "Discord üzerinden",
+ "error.page.version": "Sürüm: {{version}}",
+
+ "error.dev.rootNotFound":
+ "Kök eleman bulunamadı. index.html dosyanıza eklemeyi unuttunuz mu? Ya da id özelliği yanlış mı yazıldı?",
+
+ "error.globalSync.connectFailed": "Sunucuya bağlanılamadı. `{{url}}` adresinde çalışan bir sunucu var mı?",
+ "directory.error.invalidUrl": "URL'de geçersiz dizin.",
+
+ "error.chain.unknown": "Bilinmeyen hata",
+ "error.chain.causedBy": "Nedeni:",
+ "error.chain.apiError": "API hatası",
+ "error.chain.status": "Durum: {{status}}",
+ "error.chain.retryable": "Yeniden denenebilir: {{retryable}}",
+ "error.chain.responseBody": "Yanıt gövdesi:\n{{body}}",
+ "error.chain.didYouMean": "Bunu mu demek istediniz: {{suggestions}}",
+ "error.chain.modelNotFound": "Model bulunamadı: {{provider}}/{{model}}",
+ "error.chain.checkConfig": "Yapılandırma dosyanızı (opencode.json) sağlayıcı/model adlarını kontrol edin",
+ "error.chain.mcpFailed":
+ 'MCP sunucusu "{{name}}" başarısız oldu. Not: OpenCode henüz MCP kimlik doğrulamasını desteklemiyor.',
+ "error.chain.providerAuthFailed": "Sağlayıcı kimlik doğrulaması başarısız ({{provider}}): {{message}}",
+ "error.chain.providerInitFailed":
+ '"{{provider}}" sağlayıcısı başlatılamadı. Kimlik bilgilerini ve yapılandırmayı kontrol edin.',
+ "error.chain.configJsonInvalid": "{{path}} adresindeki yapılandırma dosyası geçerli JSON(C) değil",
+ "error.chain.configJsonInvalidWithMessage":
+ "{{path}} adresindeki yapılandırma dosyası geçerli JSON(C) değil: {{message}}",
+ "error.chain.configDirectoryTypo":
+ '"{{dir}}" dizini {{path}} içinde geçerli değil. Dizini "{{suggestion}}" olarak yeniden adlandırın veya kaldırın. Bu yaygın bir yazım hatasıdır.',
+ "error.chain.configFrontmatterError": "{{path}} içindeki ön bilgi ayrıştırılamadı:\n{{message}}",
+ "error.chain.configInvalid": "{{path}} adresindeki yapılandırma dosyası geçersiz",
+ "error.chain.configInvalidWithMessage": "{{path}} adresindeki yapılandırma dosyası geçersiz: {{message}}",
+
+ "notification.permission.title": "İzin gerekli",
+ "notification.permission.description": "{{projectName}} içindeki {{sessionTitle}} izin gerektiriyor",
+ "notification.question.title": "Soru",
+ "notification.question.description": "{{projectName}} içindeki {{sessionTitle}} bir soru soruyor",
+ "notification.action.goToSession": "Oturuma git",
+
+ "notification.session.responseReady.title": "Yanıt hazır",
+ "notification.session.error.title": "Oturum hatası",
+ "notification.session.error.fallbackDescription": "Bir hata oluştu",
+
+ "home.recentProjects": "Son projeler",
+ "home.empty.title": "Son proje yok",
+ "home.empty.description": "Yerel bir proje açarak başlayın",
+
+ "session.tab.session": "Oturum",
+ "session.tab.review": "İnceleme",
+ "session.tab.context": "Bağlam",
+ "session.panel.reviewAndFiles": "İnceleme ve dosyalar",
+ "session.review.filesChanged": "{{count}} Dosya Değişti",
+ "session.review.change.one": "Değişiklik",
+ "session.review.change.other": "Değişiklik",
+ "session.review.loadingChanges": "Değişiklikler yükleniyor...",
+ "session.review.empty": "Bu oturumda henüz değişiklik yok",
+ "session.review.noVcs": "Git VCS algılanamadı, oturum değişiklikleri tespit edilemeyecek",
+ "session.review.noChanges": "Değişiklik yok",
+
+ "session.files.selectToOpen": "Açmak için bir dosya seçin",
+ "session.files.all": "Tüm dosyalar",
+ "session.files.binaryContent": "İkili dosya (içerik görüntülenemiyor)",
+
+ "session.messages.renderEarlier": "Önceki mesajları göster",
+ "session.messages.loadingEarlier": "Önceki mesajlar yükleniyor...",
+ "session.messages.loadEarlier": "Önceki mesajları yükle",
+ "session.messages.loading": "Mesajlar yükleniyor...",
+ "session.messages.jumpToLatest": "En sona atla",
+
+ "session.context.addToContext": "{{selection}} bağlama ekle",
+ "session.todo.title": "Görevler",
+ "session.todo.collapse": "Daralt",
+ "session.todo.expand": "Genişlet",
+
+ "session.new.title": "İstediğini yap",
+ "session.new.worktree.main": "Ana dal",
+ "session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
+ "session.new.worktree.create": "Yeni çalışma ağacı oluştur",
+ "session.new.lastModified": "Son değişiklik",
+
+ "session.header.search.placeholder": "{{project}} ara",
+ "session.header.searchFiles": "Dosya ara",
+ "session.header.openIn": "Aç",
+ "session.header.open.action": "{{app}} ile aç",
+ "session.header.open.ariaLabel": "{{app}} ile aç",
+ "session.header.open.menu": "Açma seçenekleri",
+ "session.header.open.copyPath": "Yolu kopyala",
+
+ "status.popover.trigger": "Durum",
+ "status.popover.ariaLabel": "Sunucu yapılandırmaları",
+ "status.popover.tab.servers": "Sunucular",
+ "status.popover.tab.mcp": "MCP",
+ "status.popover.tab.lsp": "LSP",
+ "status.popover.tab.plugins": "Eklentiler",
+ "status.popover.action.manageServers": "Sunucuları yönet",
+
+ "session.share.popover.title": "Web'de yayınla",
+ "session.share.popover.description.shared": "Bu oturum web'de herkese açıktır. Bağlantıya sahip herkes erişebilir.",
+ "session.share.popover.description.unshared":
+ "Oturumu web'de herkese açık olarak paylaşın. Bağlantıya sahip herkes erişebilecek.",
+ "session.share.action.share": "Paylaş",
+ "session.share.action.publish": "Yayınla",
+ "session.share.action.publishing": "Yayınlanıyor...",
+ "session.share.action.unpublish": "Yayından kaldır",
+ "session.share.action.unpublishing": "Yayından kaldırılıyor...",
+ "session.share.action.view": "Görüntüle",
+ "session.share.copy.copied": "Kopyalandı",
+ "session.share.copy.copyLink": "Bağlantı kopyala",
+
+ "lsp.tooltip.none": "LSP sunucusu yok",
+ "lsp.label.connected": "{{count}} LSP",
+
+ "prompt.loading": "Komut yükleniyor...",
+ "terminal.loading": "Terminal yükleniyor...",
+ "terminal.title": "Terminal",
+ "terminal.title.numbered": "Terminal {{number}}",
+ "terminal.close": "Terminali kapat",
+ "terminal.connectionLost.title": "Bağlantı Kesildi",
+ "terminal.connectionLost.description":
+ "Terminal bağlantısı kesildi. Bu durum sunucu yeniden başladığında oluşabilir.",
+
+ "common.closeTab": "Sekmeyi kapat",
+ "common.dismiss": "Kapat",
+ "common.requestFailed": "İstek başarısız",
+ "common.moreOptions": "Daha fazla seçenek",
+ "common.learnMore": "Daha fazla bilgi",
+ "common.rename": "Yeniden adlandır",
+ "common.reset": "Sıfırla",
+ "common.archive": "Arşivle",
+ "common.delete": "Sil",
+ "common.close": "Kapat",
+ "common.edit": "Düzenle",
+ "common.loadMore": "Daha fazla yükle",
+ "common.key.esc": "ESC",
+
+ "sidebar.menu.toggle": "Menüyü aç/kapat",
+ "sidebar.nav.projectsAndSessions": "Projeler ve oturumlar",
+ "sidebar.settings": "Ayarlar",
+ "sidebar.help": "Yardım",
+ "sidebar.workspaces.enable": "Çalışma alanlarını etkinleştir",
+ "sidebar.workspaces.disable": "Çalışma alanlarını devre dışı bırak",
+ "sidebar.gettingStarted.title": "Başlarken",
+ "sidebar.gettingStarted.line1": "OpenCode ücretsiz modeller içerir, böylece hemen başlayabilirsiniz.",
+ "sidebar.gettingStarted.line2": "Claude, GPT, Gemini vb. modelleri kullanmak için herhangi bir sağlayıcı bağlayın.",
+ "sidebar.project.recentSessions": "Son oturumlar",
+ "sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
+ "sidebar.project.clearNotifications": "Bildirimleri temizle",
+
+ "app.name.desktop": "OpenCode Masaüstü",
+
+ "settings.section.desktop": "Masaüstü",
+ "settings.section.server": "Sunucu",
+ "settings.tab.general": "Genel",
+ "settings.tab.shortcuts": "Kısayollar",
+ "settings.desktop.section.wsl": "WSL",
+ "settings.desktop.wsl.title": "WSL entegrasyonu",
+ "settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
+
+ "settings.general.section.appearance": "Görünüm",
+ "settings.general.section.notifications": "Sistem bildirimleri",
+ "settings.general.section.updates": "Güncellemeler",
+ "settings.general.section.sounds": "Ses efektleri",
+ "settings.general.section.feed": "Akış",
+ "settings.general.section.display": "Ekran",
+
+ "settings.general.row.language.title": "Dil",
+ "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
+ "settings.general.row.appearance.title": "Görünüm",
+ "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
+ "settings.general.row.theme.title": "Tema",
+ "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
+ "settings.general.row.font.title": "Yazı Tipi",
+ "settings.general.row.font.description": "Kod bloklarında kullanılan monospace yazı tipini özelleştirin",
+ "settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
+ "settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
+ "settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
+ "settings.general.row.shellToolPartsExpanded.description":
+ "Zaman çizelgesinde kabuk araç bileşenlerini varsayılan olarak genişletilmiş göster",
+ "settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
+ "settings.general.row.editToolPartsExpanded.description":
+ "Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
+
+ "settings.general.row.wayland.title": "Yerel Wayland kullan",
+ "settings.general.row.wayland.description":
+ "Wayland'da X11 geri dönüşünü devre dışı bırak. Yeniden başlatma gerektirir.",
+ "settings.general.row.wayland.tooltip":
+ "Karışık yenileme hızlı monitörlere sahip Linux'ta yerel Wayland daha kararlı olabilir.",
+
+ "settings.general.row.releaseNotes.title": "Sürüm notları",
+ "settings.general.row.releaseNotes.description": "Güncellemelerden sonra Yenilikler bildirimlerini göster",
+
+ "settings.updates.row.startup.title": "Başlangıçta güncellemeleri kontrol et",
+ "settings.updates.row.startup.description": "OpenCode başladığında otomatik güncelleme kontrolü yap",
+ "settings.updates.row.check.title": "Güncellemeleri kontrol et",
+ "settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
+ "settings.updates.action.checkNow": "Şimdi kontrol et",
+ "settings.updates.action.checking": "Kontrol ediliyor...",
+ "settings.updates.toast.latest.title": "Güncelsiniz",
+ "settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
+
+ "font.option.ibmPlexMono": "IBM Plex Mono",
+ "font.option.cascadiaCode": "Cascadia Code",
+ "font.option.firaCode": "Fira Code",
+ "font.option.hack": "Hack",
+ "font.option.inconsolata": "Inconsolata",
+ "font.option.intelOneMono": "Intel One Mono",
+ "font.option.iosevka": "Iosevka",
+ "font.option.jetbrainsMono": "JetBrains Mono",
+ "font.option.mesloLgs": "Meslo LGS",
+ "font.option.robotoMono": "Roboto Mono",
+ "font.option.sourceCodePro": "Source Code Pro",
+ "font.option.ubuntuMono": "Ubuntu Mono",
+ "font.option.geistMono": "Geist Mono",
+
+ "sound.option.none": "Yok",
+ "sound.option.alert01": "Uyarı 01",
+ "sound.option.alert02": "Uyarı 02",
+ "sound.option.alert03": "Uyarı 03",
+ "sound.option.alert04": "Uyarı 04",
+ "sound.option.alert05": "Uyarı 05",
+ "sound.option.alert06": "Uyarı 06",
+ "sound.option.alert07": "Uyarı 07",
+ "sound.option.alert08": "Uyarı 08",
+ "sound.option.alert09": "Uyarı 09",
+ "sound.option.alert10": "Uyarı 10",
+ "sound.option.bipbop01": "Bip-bop 01",
+ "sound.option.bipbop02": "Bip-bop 02",
+ "sound.option.bipbop03": "Bip-bop 03",
+ "sound.option.bipbop04": "Bip-bop 04",
+ "sound.option.bipbop05": "Bip-bop 05",
+ "sound.option.bipbop06": "Bip-bop 06",
+ "sound.option.bipbop07": "Bip-bop 07",
+ "sound.option.bipbop08": "Bip-bop 08",
+ "sound.option.bipbop09": "Bip-bop 09",
+ "sound.option.bipbop10": "Bip-bop 10",
+ "sound.option.staplebops01": "Staplebops 01",
+ "sound.option.staplebops02": "Staplebops 02",
+ "sound.option.staplebops03": "Staplebops 03",
+ "sound.option.staplebops04": "Staplebops 04",
+ "sound.option.staplebops05": "Staplebops 05",
+ "sound.option.staplebops06": "Staplebops 06",
+ "sound.option.staplebops07": "Staplebops 07",
+ "sound.option.nope01": "Hayır 01",
+ "sound.option.nope02": "Hayır 02",
+ "sound.option.nope03": "Hayır 03",
+ "sound.option.nope04": "Hayır 04",
+ "sound.option.nope05": "Hayır 05",
+ "sound.option.nope06": "Hayır 06",
+ "sound.option.nope07": "Hayır 07",
+ "sound.option.nope08": "Hayır 08",
+ "sound.option.nope09": "Hayır 09",
+ "sound.option.nope10": "Hayır 10",
+ "sound.option.nope11": "Hayır 11",
+ "sound.option.nope12": "Hayır 12",
+ "sound.option.yup01": "Evet 01",
+ "sound.option.yup02": "Evet 02",
+ "sound.option.yup03": "Evet 03",
+ "sound.option.yup04": "Evet 04",
+ "sound.option.yup05": "Evet 05",
+ "sound.option.yup06": "Evet 06",
+
+ "settings.general.notifications.agent.title": "Ajan",
+ "settings.general.notifications.agent.description":
+ "Ajan tamamlandığında veya dikkat gerektirdiğinde sistem bildirimi göster",
+ "settings.general.notifications.permissions.title": "İzinler",
+ "settings.general.notifications.permissions.description": "İzin gerektiğinde sistem bildirimi göster",
+ "settings.general.notifications.errors.title": "Hatalar",
+ "settings.general.notifications.errors.description": "Hata oluştuğunda sistem bildirimi göster",
+
+ "settings.general.sounds.agent.title": "Ajan",
+ "settings.general.sounds.agent.description": "Ajan tamamlandığında veya dikkat gerektirdiğinde ses çal",
+ "settings.general.sounds.permissions.title": "İzinler",
+ "settings.general.sounds.permissions.description": "İzin gerektiğinde ses çal",
+ "settings.general.sounds.errors.title": "Hatalar",
+ "settings.general.sounds.errors.description": "Hata oluştuğunda ses çal",
+
+ "settings.shortcuts.title": "Klavye kısayolları",
+ "settings.shortcuts.reset.button": "Varsayılanlara sıfırla",
+ "settings.shortcuts.reset.toast.title": "Kısayollar sıfırlandı",
+ "settings.shortcuts.reset.toast.description": "Klavye kısayolları varsayılanlara sıfırlandı.",
+ "settings.shortcuts.conflict.title": "Kısayol zaten kullanılıyor",
+ "settings.shortcuts.conflict.description": "{{keybind}} zaten {{titles}} için atanmış.",
+ "settings.shortcuts.unassigned": "Atanmamış",
+ "settings.shortcuts.pressKeys": "Tuşlara basın",
+ "settings.shortcuts.search.placeholder": "Kısayol ara",
+ "settings.shortcuts.search.empty": "Kısayol bulunamadı",
+
+ "settings.shortcuts.group.general": "Genel",
+ "settings.shortcuts.group.session": "Oturum",
+ "settings.shortcuts.group.navigation": "Gezinme",
+ "settings.shortcuts.group.modelAndAgent": "Model ve ajan",
+ "settings.shortcuts.group.terminal": "Terminal",
+ "settings.shortcuts.group.prompt": "Komut",
+
+ "settings.providers.title": "Sağlayıcılar",
+ "settings.providers.description": "Sağlayıcı ayarları burada yapılandırılabilecek.",
+ "settings.providers.section.connected": "Bağlı sağlayıcılar",
+ "settings.providers.connected.empty": "Bağlı sağlayıcı yok",
+ "settings.providers.section.popular": "Popüler sağlayıcılar",
+ "settings.providers.tag.environment": "Ortam",
+ "settings.providers.tag.config": "Yapılandırma",
+ "settings.providers.tag.custom": "Özel",
+ "settings.providers.tag.other": "Diğer",
+ "settings.models.title": "Modeller",
+ "settings.models.description": "Model ayarları burada yapılandırılabilecek.",
+ "settings.agents.title": "Ajanlar",
+ "settings.agents.description": "Ajan ayarları burada yapılandırılabilecek.",
+ "settings.commands.title": "Komutlar",
+ "settings.commands.description": "Komut ayarları burada yapılandırılabilecek.",
+ "settings.mcp.title": "MCP",
+ "settings.mcp.description": "MCP ayarları burada yapılandırılabilecek.",
+
+ "settings.permissions.title": "İzinler",
+ "settings.permissions.description": "Sunucunun varsayılan olarak hangi araçları kullanabileceğini kontrol edin.",
+ "settings.permissions.section.tools": "Araçlar",
+ "settings.permissions.toast.updateFailed.title": "İzinler güncellenemedi",
+
+ "settings.permissions.action.allow": "İzin Ver",
+ "settings.permissions.action.ask": "Sor",
+ "settings.permissions.action.deny": "Reddet",
+
+ "settings.permissions.tool.read.title": "Oku",
+ "settings.permissions.tool.read.description": "Bir dosyayı okuma (dosya yoluyla eşleşir)",
+ "settings.permissions.tool.edit.title": "Düzenle",
+ "settings.permissions.tool.edit.description": "Düzenleme, yazma, yama ve çoklu düzenleme dahil dosyaları değiştir",
+ "settings.permissions.tool.glob.title": "Glob",
+ "settings.permissions.tool.glob.description": "Glob kalıpları kullanarak dosyaları eşle",
+ "settings.permissions.tool.grep.title": "Grep",
+ "settings.permissions.tool.grep.description": "Düzenli ifadeler kullanarak dosya içerikleri ara",
+ "settings.permissions.tool.list.title": "Listele",
+ "settings.permissions.tool.list.description": "Bir dizindeki dosyaları listele",
+ "settings.permissions.tool.bash.title": "Bash",
+ "settings.permissions.tool.bash.description": "Kabuk komutları çalıştır",
+ "settings.permissions.tool.task.title": "Görev",
+ "settings.permissions.tool.task.description": "Alt ajanlar başlat",
+ "settings.permissions.tool.skill.title": "Beceri",
+ "settings.permissions.tool.skill.description": "Ada göre bir beceri yükle",
+ "settings.permissions.tool.lsp.title": "LSP",
+ "settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır",
+ "settings.permissions.tool.todoread.title": "Görev Oku",
+ "settings.permissions.tool.todoread.description": "Görev listesini oku",
+ "settings.permissions.tool.todowrite.title": "Görev Yaz",
+ "settings.permissions.tool.todowrite.description": "Görev listesini güncelle",
+ "settings.permissions.tool.webfetch.title": "Web Getir",
+ "settings.permissions.tool.webfetch.description": "Bir URL'den içerik getir",
+ "settings.permissions.tool.websearch.title": "Web Ara",
+ "settings.permissions.tool.websearch.description": "Web'de ara",
+ "settings.permissions.tool.codesearch.title": "Kod Ara",
+ "settings.permissions.tool.codesearch.description": "Web'de kod ara",
+ "settings.permissions.tool.external_directory.title": "Harici Dizin",
+ "settings.permissions.tool.external_directory.description": "Proje dizini dışındaki dosyalara eriş",
+ "settings.permissions.tool.doom_loop.title": "Sonsuz Döngü",
+ "settings.permissions.tool.doom_loop.description": "Aynı girdiyle tekrarlanan araç çağrılarını algıla",
+
+ "session.delete.failed.title": "Oturum silinemedi",
+ "session.delete.title": "Oturumu sil",
+ "session.delete.confirm": '"{{name}}" oturumu silinsin mi?',
+ "session.delete.button": "Oturumu sil",
+
+ "workspace.new": "Yeni çalışma alanı",
+ "workspace.type.local": "yerel",
+ "workspace.type.sandbox": "sandbox",
+ "workspace.create.failed.title": "Çalışma alanı oluşturulamadı",
+ "workspace.delete.failed.title": "Çalışma alanı silinemedi",
+ "workspace.resetting.title": "Çalışma alanı sıfırlanıyor",
+ "workspace.resetting.description": "Bu bir dakika sürebilir.",
+ "workspace.reset.failed.title": "Çalışma alanı sıfırlanamadı",
+ "workspace.reset.success.title": "Çalışma alanı sıfırlandı",
+ "workspace.reset.success.description": "Çalışma alanı artık varsayılan dalla eşleşiyor.",
+ "workspace.error.stillPreparing": "Çalışma alanı hâlâ hazırlanıyor",
+ "workspace.status.checking": "Birleşmemiş değişiklikler kontrol ediliyor...",
+ "workspace.status.error": "Git durumu doğrulanamadı.",
+ "workspace.status.clean": "Birleşmemiş değişiklik algılanmadı.",
+ "workspace.status.dirty": "Bu çalışma alanında birleşmemiş değişiklikler algılandı.",
+ "workspace.delete.title": "Çalışma alanını sil",
+ "workspace.delete.confirm": '"{{name}}" çalışma alanı silinsin mi?',
+ "workspace.delete.button": "Çalışma alanını sil",
+ "workspace.reset.title": "Çalışma alanını sıfırla",
+ "workspace.reset.confirm": '"{{name}}" çalışma alanı sıfırlansın mı?',
+ "workspace.reset.button": "Çalışma alanını sıfırla",
+ "workspace.reset.archived.none": "Arşivlenecek aktif oturum yok.",
+ "workspace.reset.archived.one": "1 oturum arşivlenecek.",
+ "workspace.reset.archived.many": "{{count}} oturum arşivlenecek.",
+ "workspace.reset.note": "Bu işlem çalışma alanını varsayılan dalla eşleşecek şekilde sıfırlayacak.",
+ "common.open": "Aç",
+ "dialog.releaseNotes.action.getStarted": "Başla",
+ "dialog.releaseNotes.action.next": "İleri",
+ "dialog.releaseNotes.action.hideFuture": "Bunu gelecekte bir daha gösterme",
+ "dialog.releaseNotes.media.alt": "Sürüm önizlemesi",
+ "toast.project.reloadFailed.title": "{{project}} yeniden yüklenemedi",
+ "error.server.invalidConfiguration": "Geçersiz yapılandırma",
+ "common.moreCountSuffix": " (+{{count}} daha)",
+ "common.time.justNow": "Şimdi",
+ "common.time.minutesAgo.short": "{{count}}dk önce",
+ "common.time.hoursAgo.short": "{{count}}sa önce",
+ "common.time.daysAgo.short": "{{count}}g önce",
+ "settings.providers.connected.environmentDescription": "Ortam değişkenlerinizden bağlandı",
+ "settings.providers.custom.description": "Temel URL üzerinden OpenAI uyumlu bir sağlayıcı ekleyin.",
+} satisfies Partial>
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index 42740fa771..1f88a82223 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": "加载文件失败",
@@ -508,6 +510,7 @@ export const dict = {
"session.todo.title": "待办事项",
"session.todo.collapse": "折叠",
"session.todo.expand": "展开",
+ "session.new.title": "构建任何东西",
"session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
"session.new.worktree.create": "创建新的 worktree",
@@ -595,6 +598,7 @@ export const dict = {
"settings.general.section.notifications": "系统通知",
"settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效",
+ "settings.general.section.feed": "动态",
"settings.general.section.display": "显示",
"settings.general.row.language.title": "语言",
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
@@ -604,6 +608,10 @@ export const dict = {
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
"settings.general.row.font.title": "字体",
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
+ "settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
+ "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
+ "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
+ "settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
"settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
@@ -633,6 +641,7 @@ export const dict = {
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "无",
"sound.option.alert01": "警报 01",
"sound.option.alert02": "警报 02",
"sound.option.alert03": "警报 03",
@@ -801,4 +810,18 @@ export const dict = {
"workspace.reset.archived.one": "将归档 1 个会话。",
"workspace.reset.archived.many": "将归档 {{count}} 个会话。",
"workspace.reset.note": "这将把工作区重置为与默认分支一致。",
+ "common.open": "打开",
+ "dialog.releaseNotes.action.getStarted": "开始",
+ "dialog.releaseNotes.action.next": "下一步",
+ "dialog.releaseNotes.action.hideFuture": "不再显示",
+ "dialog.releaseNotes.media.alt": "发布预览",
+ "toast.project.reloadFailed.title": "无法重新加载 {{project}}",
+ "error.server.invalidConfiguration": "配置无效",
+ "common.moreCountSuffix": " (还有 {{count}} 个)",
+ "common.time.justNow": "刚刚",
+ "common.time.minutesAgo.short": "{{count}}分钟前",
+ "common.time.hoursAgo.short": "{{count}}小时前",
+ "common.time.daysAgo.short": "{{count}}天前",
+ "settings.providers.connected.environmentDescription": "已通过环境变量连接",
+ "settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。",
} satisfies Partial>
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index f47fdede8f..a75e8ef47a 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": "請先連線提供者以總結此工作階段",
@@ -505,6 +507,7 @@ export const dict = {
"session.todo.collapse": "折疊",
"session.todo.expand": "展開",
+ "session.new.title": "建構任何東西",
"session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
"session.new.worktree.create": "建立新的 worktree",
@@ -589,6 +592,7 @@ export const dict = {
"settings.general.section.notifications": "系統通知",
"settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效",
+ "settings.general.section.feed": "資訊流",
"settings.general.section.display": "顯示",
"settings.general.row.language.title": "語言",
@@ -600,6 +604,10 @@ export const dict = {
"settings.general.row.font.title": "字型",
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
+ "settings.general.row.shellToolPartsExpanded.title": "展開 shell 工具區塊",
+ "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
+ "settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
+ "settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
"settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
@@ -629,6 +637,7 @@ export const dict = {
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"font.option.geistMono": "Geist Mono",
+ "sound.option.none": "無",
"sound.option.alert01": "警報 01",
"sound.option.alert02": "警報 02",
"sound.option.alert03": "警報 03",
@@ -796,4 +805,18 @@ export const dict = {
"workspace.reset.archived.one": "將封存 1 個工作階段。",
"workspace.reset.archived.many": "將封存 {{count}} 個工作階段。",
"workspace.reset.note": "這將把工作區重設為與預設分支一致。",
+ "common.open": "打開",
+ "dialog.releaseNotes.action.getStarted": "開始",
+ "dialog.releaseNotes.action.next": "下一步",
+ "dialog.releaseNotes.action.hideFuture": "不再顯示",
+ "dialog.releaseNotes.media.alt": "發佈預覽",
+ "toast.project.reloadFailed.title": "無法重新載入 {{project}}",
+ "error.server.invalidConfiguration": "無效的設定",
+ "common.moreCountSuffix": " (還有 {{count}} 個)",
+ "common.time.justNow": "剛剛",
+ "common.time.minutesAgo.short": "{{count}}分鐘前",
+ "common.time.hoursAgo.short": "{{count}}小時前",
+ "common.time.daysAgo.short": "{{count}}天前",
+ "settings.providers.connected.environmentDescription": "已從環境變數連線",
+ "settings.providers.custom.description": "透過基本 URL 新增與 OpenAI 相容的提供者。",
} satisfies Partial>
diff --git a/packages/app/src/index.css b/packages/app/src/index.css
index 4af87bca63..9e231e2d28 100644
--- a/packages/app/src/index.css
+++ b/packages/app/src/index.css
@@ -1 +1,29 @@
@import "@opencode-ai/ui/styles/tailwind";
+
+@layer components {
+ [data-component="getting-started"] {
+ container-type: inline-size;
+ container-name: getting-started;
+ }
+
+ [data-component="getting-started-actions"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem; /* gap-3 */
+ }
+
+ [data-component="getting-started-actions"] > [data-component="button"] {
+ width: 100%;
+ }
+
+ @container getting-started (min-width: 17rem) {
+ [data-component="getting-started-actions"] {
+ flex-direction: row;
+ align-items: center;
+ }
+
+ [data-component="getting-started-actions"] > [data-component="button"] {
+ width: auto;
+ }
+ }
+}
diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx
index 4f1d93ab28..fdf321f2dc 100644
--- a/packages/app/src/pages/directory-layout.tsx
+++ b/packages/app/src/pages/directory-layout.tsx
@@ -1,35 +1,27 @@
-import { createEffect, createMemo, Show, type ParentProps } from "solid-js"
+import { batch, createEffect, createMemo, Show, type ParentProps } from "solid-js"
import { createStore } from "solid-js/store"
-import { useNavigate, useParams } from "@solidjs/router"
-import { SDKProvider, useSDK } from "@/context/sdk"
+import { useLocation, useNavigate, useParams } from "@solidjs/router"
+import { SDKProvider } from "@/context/sdk"
import { SyncProvider, useSync } from "@/context/sync"
import { LocalProvider } from "@/context/local"
+import { useGlobalSDK } from "@/context/global-sdk"
import { DataProvider } from "@opencode-ai/ui/context"
-import type { QuestionAnswer } from "@opencode-ai/sdk/v2"
+import { base64Encode } from "@opencode-ai/util/encode"
import { decode64 } from "@/utils/base64"
import { showToast } from "@opencode-ai/ui/toast"
import { useLanguage } from "@/context/language"
-
function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
- const params = useParams()
const navigate = useNavigate()
const sync = useSync()
- const sdk = useSDK()
+ const slug = createMemo(() => base64Encode(props.directory))
return (
sdk.client.permission.respond(input)}
- onQuestionReply={(input: { requestID: string; answers: QuestionAnswer[] }) => sdk.client.question.reply(input)}
- onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
- onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
- onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
+ onNavigateToSession={(sessionID: string) => navigate(`/${slug()}/session/${sessionID}`)}
+ onSessionHref={(sessionID: string) => `/${slug()}/session/${sessionID}`}
>
{props.children}
@@ -39,31 +31,63 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
export default function Layout(props: ParentProps) {
const params = useParams()
const navigate = useNavigate()
+ const location = useLocation()
const language = useLanguage()
- const [store, setStore] = createStore({ invalid: "" })
- const directory = createMemo(() => {
- return decode64(params.dir) ?? ""
- })
+ const globalSDK = useGlobalSDK()
+ const directory = createMemo(() => decode64(params.dir) ?? "")
+ const [state, setState] = createStore({ invalid: "", resolved: "" })
createEffect(() => {
if (!params.dir) return
- if (directory()) return
- if (store.invalid === params.dir) return
- setStore("invalid", params.dir)
- showToast({
- variant: "error",
- title: language.t("common.requestFailed"),
- description: language.t("directory.error.invalidUrl"),
- })
- navigate("/", { replace: true })
+ const raw = directory()
+ if (!raw) {
+ if (state.invalid === params.dir) return
+ setState("invalid", params.dir)
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: language.t("directory.error.invalidUrl"),
+ })
+ navigate("/", { replace: true })
+ return
+ }
+
+ const current = params.dir
+ globalSDK
+ .createClient({
+ directory: raw,
+ throwOnError: true,
+ })
+ .path.get()
+ .then((x) => {
+ if (params.dir !== current) return
+ const next = x.data?.directory ?? raw
+ batch(() => {
+ setState("invalid", "")
+ setState("resolved", next)
+ })
+ if (next === raw) return
+ const path = location.pathname.slice(current.length + 1)
+ navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
+ })
+ .catch(() => {
+ if (params.dir !== current) return
+ batch(() => {
+ setState("invalid", "")
+ setState("resolved", raw)
+ })
+ })
})
+
return (
-
-
-
- {props.children}
-
-
+
+ {(resolved) => (
+
+
+ {props.children}
+
+
+ )}
)
}
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 62094a6e42..052a03c549 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -10,9 +10,8 @@ import {
ParentProps,
Show,
untrack,
- type JSX,
} from "solid-js"
-import { A, useNavigate, useParams } from "@solidjs/router"
+import { useNavigate, useParams } from "@solidjs/router"
import { useLayout, LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
import { Persist, persisted } from "@/utils/persist"
@@ -20,9 +19,8 @@ import { base64Encode } from "@opencode-ai/util/encode"
import { decode64 } from "@/utils/base64"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Button } from "@opencode-ai/ui/button"
-import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
-import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Dialog } from "@opencode-ai/ui/dialog"
import { getFilename } from "@opencode-ai/util/path"
@@ -36,13 +34,16 @@ import { useProviders } from "@/hooks/use-providers"
import { showToast, Toast, toaster } from "@opencode-ai/ui/toast"
import { useGlobalSDK } from "@/context/global-sdk"
import { clearWorkspaceTerminals } from "@/context/terminal"
+import { dropSessionCaches, pickSessionCacheEvictions } from "@/context/global-sync/session-cache"
import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
import { playSound, soundSrc } from "@/utils/sound"
import { createAim } from "@/utils/aim"
+import { setNavigate } from "@/utils/notification-click"
import { Worktree as WorktreeState } from "@/utils/worktree"
+import { setSessionHandoff } from "@/pages/session/handoff"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
@@ -53,19 +54,25 @@ import { useCommand, type CommandOption } from "@/context/command"
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
import { DialogEditProject } from "@/components/dialog-edit-project"
+import { DebugBar } from "@/components/debug-bar"
import { Titlebar } from "@/components/titlebar"
import { useServer } from "@/context/server"
import { useLanguage, type Locale } from "@/context/language"
import {
- childMapByParent,
displayName,
+ effectiveWorkspaceOrder,
errorMessage,
getDraggableId,
+ latestRootSession,
sortedRootSessions,
- syncWorkspaceOrder,
workspaceKey,
} from "./layout/helpers"
-import { collectOpenProjectDeepLinks, deepLinkEvent, drainPendingDeepLinks } from "./layout/deep-links"
+import {
+ collectNewSessionDeepLinks,
+ collectOpenProjectDeepLinks,
+ deepLinkEvent,
+ drainPendingDeepLinks,
+} from "./layout/deep-links"
import { createInlineEditorController } from "./layout/inline-editor"
import {
LocalWorkspace,
@@ -88,6 +95,7 @@ export default function Layout(props: ParentProps) {
workspaceName: {} as Record,
workspaceBranchName: {} as Record>,
workspaceExpanded: {} as Record,
+ gettingStartedDismissed: false,
}),
)
@@ -106,6 +114,7 @@ export default function Layout(props: ParentProps) {
const notification = useNotification()
const permission = usePermission()
const navigate = useNavigate()
+ setNavigate(navigate)
const providers = useProviders()
const dialog = useDialog()
const command = useCommand()
@@ -148,6 +157,8 @@ export default function Layout(props: ParentProps) {
const isBusy = (directory: string) => !!state.busyWorkspaces[workspaceKey(directory)]
const navLeave = { current: undefined as number | undefined }
const [sortNow, setSortNow] = createSignal(Date.now())
+ const [sizing, setSizing] = createSignal(false)
+ let sizet: number | undefined
let sortNowInterval: ReturnType | undefined
const sortNowTimeout = setTimeout(
() => {
@@ -160,7 +171,7 @@ export default function Layout(props: ParentProps) {
const aim = createAim({
enabled: () => !layout.sidebar.opened(),
active: () => state.hoverProject,
- el: () => state.nav,
+ el: () => state.nav?.querySelector("[data-component='sidebar-rail']") ?? state.nav,
onActivate: (directory) => {
globalSync.child(directory)
setState("hoverProject", directory)
@@ -172,9 +183,23 @@ export default function Layout(props: ParentProps) {
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
clearTimeout(sortNowTimeout)
if (sortNowInterval) clearInterval(sortNowInterval)
+ if (sizet !== undefined) clearTimeout(sizet)
+ if (peekt !== undefined) clearTimeout(peekt)
aim.reset()
})
+ onMount(() => {
+ const stop = () => setSizing(false)
+ window.addEventListener("pointerup", stop)
+ window.addEventListener("pointercancel", stop)
+ window.addEventListener("blur", stop)
+ onCleanup(() => {
+ window.removeEventListener("pointerup", stop)
+ window.removeEventListener("pointercancel", stop)
+ window.removeEventListener("blur", stop)
+ })
+ })
+
const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined)
const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering())
const setHoverProject = (value: string | undefined) => {
@@ -185,12 +210,54 @@ export default function Layout(props: ParentProps) {
const clearHoverProjectSoon = () => queueMicrotask(() => setHoverProject(undefined))
const setHoverSession = (id: string | undefined) => setState("hoverSession", id)
+ const disarm = () => {
+ if (navLeave.current === undefined) return
+ clearTimeout(navLeave.current)
+ navLeave.current = undefined
+ }
+
+ const arm = () => {
+ if (layout.sidebar.opened()) return
+ if (state.hoverProject === undefined) return
+ disarm()
+ navLeave.current = window.setTimeout(() => {
+ navLeave.current = undefined
+ setHoverProject(undefined)
+ setState("hoverSession", undefined)
+ }, 300)
+ }
+
+ const [peek, setPeek] = createSignal(undefined)
+ const [peeked, setPeeked] = createSignal(false)
+ let peekt: number | undefined
+
const hoverProjectData = createMemo(() => {
const id = state.hoverProject
if (!id) return
return layout.projects.list().find((project) => project.worktree === id)
})
+ createEffect(() => {
+ const p = hoverProjectData()
+ if (p) {
+ if (peekt !== undefined) {
+ clearTimeout(peekt)
+ peekt = undefined
+ }
+ setPeek(p)
+ setPeeked(true)
+ return
+ }
+
+ setPeeked(false)
+ if (peek() === undefined) return
+ if (peekt !== undefined) clearTimeout(peekt)
+ peekt = window.setTimeout(() => {
+ peekt = undefined
+ setPeek(undefined)
+ }, 180)
+ })
+
createEffect(() => {
if (!layout.sidebar.opened()) return
setHoverProject(undefined)
@@ -358,6 +425,17 @@ export default function Layout(props: ParentProps) {
return
}
+ if (
+ e.details?.type === "question.replied" ||
+ e.details?.type === "question.rejected" ||
+ e.details?.type === "permission.replied"
+ ) {
+ const props = e.details.properties as { sessionID: string }
+ const sessionKey = `${e.name}:${props.sessionID}`
+ dismissSessionAlert(sessionKey)
+ return
+ }
+
if (e.details?.type !== "permission.asked" && e.details?.type !== "question.asked") return
const title =
e.details.type === "permission.asked"
@@ -480,21 +558,6 @@ export default function Layout(props: ParentProps) {
return projects.find((p) => p.worktree === root)
})
- createEffect(
- on(
- () => ({ ready: pageReady(), project: currentProject() }),
- (value) => {
- if (!value.ready) return
- const project = value.project
- if (!project) return
- const last = server.projects.last()
- if (last === project.worktree) return
- server.projects.touch(project.worktree)
- },
- { defer: true },
- ),
- )
-
createEffect(
on(
() => ({ ready: pageReady(), layoutReady: layoutReady(), dir: params.dir, list: layout.projects.list() }),
@@ -553,29 +616,17 @@ export default function Layout(props: ParentProps) {
return layout.sidebar.workspaces(project.worktree)()
})
- createEffect(() => {
- if (!pageReady()) return
- if (!layoutReady()) return
+ const visibleSessionDirs = createMemo(() => {
const project = currentProject()
- if (!project) return
+ if (!project) return [] as string[]
+ if (!workspaceSetting()) return [project.worktree]
- const local = project.worktree
- const dirs = [project.worktree, ...(project.sandboxes ?? [])]
- const existing = store.workspaceOrder[project.worktree]
- const merged = syncWorkspaceOrder(local, dirs, existing)
- if (!existing) {
- setStore("workspaceOrder", project.worktree, merged)
- return
- }
-
- if (merged.length !== existing.length) {
- setStore("workspaceOrder", project.worktree, merged)
- return
- }
-
- if (merged.some((d, i) => d !== existing[i])) {
- setStore("workspaceOrder", project.worktree, merged)
- }
+ const activeDir = currentDir()
+ return workspaceIds(project).filter((directory) => {
+ const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
+ const active = directory === activeDir
+ return expanded || active
+ })
})
createEffect(() => {
@@ -592,25 +643,17 @@ export default function Layout(props: ParentProps) {
})
const currentSessions = createMemo(() => {
- const project = currentProject()
- if (!project) return [] as Session[]
const now = Date.now()
- if (workspaceSetting()) {
- const dirs = workspaceIds(project)
- const activeDir = currentDir()
- const result: Session[] = []
- for (const dir of dirs) {
- const expanded = store.workspaceExpanded[dir] ?? dir === project.worktree
- const active = dir === activeDir
- if (!expanded && !active) continue
- const [dirStore] = globalSync.child(dir, { bootstrap: true })
- const dirSessions = sortedRootSessions(dirStore, now)
- result.push(...dirSessions)
- }
- return result
+ const dirs = visibleSessionDirs()
+ if (dirs.length === 0) return [] as Session[]
+
+ const result: Session[] = []
+ for (const dir of dirs) {
+ const [dirStore] = globalSync.child(dir, { bootstrap: true })
+ const dirSessions = sortedRootSessions(dirStore, now)
+ result.push(...dirSessions)
}
- const [projectStore] = globalSync.child(project.worktree)
- return sortedRootSessions(projectStore, now)
+ return result
})
type PrefetchQueue = {
@@ -627,25 +670,24 @@ export default function Layout(props: ParentProps) {
const prefetchQueues = new Map()
const PREFETCH_MAX_SESSIONS_PER_DIR = 10
- const prefetchedByDir = new Map>()
+ const prefetchedByDir = new Map>()
const lruFor = (directory: string) => {
const existing = prefetchedByDir.get(directory)
if (existing) return existing
- const created = new Map()
+ const created = new Set()
prefetchedByDir.set(directory, created)
return created
}
const markPrefetched = (directory: string, sessionID: string) => {
const lru = lruFor(directory)
- if (lru.has(sessionID)) lru.delete(sessionID)
- lru.set(sessionID, true)
- while (lru.size > PREFETCH_MAX_SESSIONS_PER_DIR) {
- const oldest = lru.keys().next().value as string | undefined
- if (!oldest) return
- lru.delete(oldest)
- }
+ return pickSessionCacheEvictions({
+ seen: lru,
+ keep: sessionID,
+ limit: PREFETCH_MAX_SESSIONS_PER_DIR,
+ preserve: directory === params.dir && params.id ? [params.id] : undefined,
+ })
}
createEffect(() => {
@@ -694,6 +736,7 @@ export default function Layout(props: ParentProps) {
return retry(() => globalSDK.client.session.messages({ directory, sessionID, limit: prefetchChunk }))
.then((messages) => {
if (prefetchToken.value !== token) return
+ if (!lruFor(directory).has(sessionID)) return
const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
const next = items.map((x) => x.info).filter((m): m is Message => !!m?.id)
@@ -757,7 +800,18 @@ export default function Layout(props: ParentProps) {
const lru = lruFor(directory)
const known = lru.has(session.id)
if (!known && lru.size >= PREFETCH_MAX_SESSIONS_PER_DIR && priority !== "high") return
- markPrefetched(directory, session.id)
+ const stale = markPrefetched(directory, session.id)
+ if (stale.length > 0) {
+ const [, setStore] = globalSync.child(directory, { bootstrap: false })
+ for (const id of stale) {
+ globalSync.todo.set(id, undefined)
+ }
+ setStore(
+ produce((draft) => {
+ dropSessionCaches(draft, stale)
+ }),
+ )
+ }
if (priority === "high") q.pending.unshift(session.id)
if (priority !== "high") q.pending.push(session.id)
@@ -825,7 +879,6 @@ export default function Layout(props: ParentProps) {
}
navigateToSession(session)
- queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
}
function navigateSessionByUnseen(offset: number) {
@@ -860,7 +913,6 @@ export default function Layout(props: ParentProps) {
}
navigateToSession(session)
- queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
return
}
}
@@ -1093,14 +1145,112 @@ export default function Layout(props: ParentProps) {
return meta?.worktree ?? directory
}
- function navigateToProject(directory: string | undefined) {
+ function activeProjectRoot(directory: string) {
+ return currentProject()?.worktree ?? projectRoot(directory)
+ }
+
+ function touchProjectRoute() {
+ const root = currentProject()?.worktree
+ if (!root) return
+ if (server.projects.last() !== root) server.projects.touch(root)
+ return root
+ }
+
+ function rememberSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
+ setStore("lastProjectSession", root, { directory, id, at: Date.now() })
+ return root
+ }
+
+ function clearLastProjectSession(root: string) {
+ if (!store.lastProjectSession[root]) return
+ setStore(
+ "lastProjectSession",
+ produce((draft) => {
+ delete draft[root]
+ }),
+ )
+ }
+
+ function syncSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
+ rememberSessionRoute(directory, id, root)
+ notification.session.markViewed(id)
+ const expanded = untrack(() => store.workspaceExpanded[directory])
+ if (expanded === false) {
+ setStore("workspaceExpanded", directory, true)
+ }
+ requestAnimationFrame(() => scrollToSession(id, `${directory}:${id}`))
+ return root
+ }
+
+ 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)
+ let dirs = project
+ ? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
+ : [root]
+ const canOpen = (value: string | undefined) => {
+ if (!value) return false
+ return dirs.some((item) => workspaceKey(item) === workspaceKey(value))
+ }
+ const refreshDirs = async (target?: string) => {
+ if (!target || target === root || canOpen(target)) return canOpen(target)
+ const listed = await globalSDK.client.worktree
+ .list({ directory: root })
+ .then((x) => x.data ?? [])
+ .catch(() => [] as string[])
+ dirs = effectiveWorkspaceOrder(root, [root, ...listed], store.workspaceOrder[root])
+ return canOpen(target)
+ }
+ const openSession = async (target: { directory: string; id: string }) => {
+ if (!canOpen(target.directory)) return false
+ const [data] = globalSync.child(target.directory, { bootstrap: false })
+ if (data.session.some((item) => item.id === target.id)) {
+ setStore("lastProjectSession", root, { directory: target.directory, id: target.id, at: Date.now() })
+ navigateWithSidebarReset(`/${base64Encode(target.directory)}/session/${target.id}`)
+ return true
+ }
+ const resolved = await globalSDK.client.session
+ .get({ sessionID: target.id })
+ .then((x) => x.data)
+ .catch(() => undefined)
+ if (!resolved?.directory) return false
+ if (!canOpen(resolved.directory)) return false
+ setStore("lastProjectSession", root, { directory: resolved.directory, id: resolved.id, at: Date.now() })
+ navigateWithSidebarReset(`/${base64Encode(resolved.directory)}/session/${resolved.id}`)
+ return true
+ }
const projectSession = store.lastProjectSession[root]
if (projectSession?.id) {
- navigateWithSidebarReset(`/${base64Encode(projectSession.directory)}/session/${projectSession.id}`)
+ await refreshDirs(projectSession.directory)
+ const opened = await openSession(projectSession)
+ if (opened) return
+ clearLastProjectSession(root)
+ }
+
+ 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
}
@@ -1119,9 +1269,20 @@ export default function Layout(props: ParentProps) {
const handleDeepLinks = (urls: string[]) => {
if (!server.isLocal()) return
+
for (const directory of collectOpenProjectDeepLinks(urls)) {
openProject(directory)
}
+
+ for (const link of collectNewSessionDeepLinks(urls)) {
+ openProject(link.directory, false)
+ const slug = base64Encode(link.directory)
+ if (link.prompt) {
+ setSessionHandoff(slug, { prompt: link.prompt })
+ }
+ const href = link.prompt ? `/${slug}/session?prompt=${encodeURIComponent(link.prompt)}` : `/${slug}/session`
+ navigateWithSidebarReset(href)
+ }
}
onMount(() => {
@@ -1157,11 +1318,28 @@ export default function Layout(props: ParentProps) {
}
function closeProject(directory: string) {
- const index = layout.projects.list().findIndex((x) => x.worktree === directory)
- const next = layout.projects.list()[index + 1]
+ const list = layout.projects.list()
+ const index = list.findIndex((x) => x.worktree === directory)
+ const active = currentProject()?.worktree === directory
+ if (index === -1) return
+ const next = list[index + 1]
+
+ if (!active) {
+ layout.projects.close(directory)
+ return
+ }
+
+ if (!next) {
+ layout.projects.close(directory)
+ navigate("/")
+ return
+ }
+
+ navigateWithSidebarReset(`/${base64Encode(next.worktree)}/session`)
layout.projects.close(directory)
- if (next) navigateToProject(next.worktree)
- else navigate("/")
+ queueMicrotask(() => {
+ void navigateToProject(next.worktree)
+ })
}
function toggleProjectWorkspaces(project: LocalProject) {
@@ -1202,9 +1380,17 @@ export default function Layout(props: ParentProps) {
}
}
- const deleteWorkspace = async (root: string, directory: string) => {
+ const deleteWorkspace = async (root: string, directory: string, leaveDeletedWorkspace = false) => {
if (directory === root) return
+ const current = currentDir()
+ const currentKey = workspaceKey(current)
+ const deletedKey = workspaceKey(directory)
+ const shouldLeave = leaveDeletedWorkspace || (!!params.dir && currentKey === deletedKey)
+ if (!leaveDeletedWorkspace && shouldLeave) {
+ navigateWithSidebarReset(`/${base64Encode(root)}/session`)
+ }
+
setBusy(directory, true)
const result = await globalSDK.client.worktree
@@ -1222,6 +1408,10 @@ export default function Layout(props: ParentProps) {
if (!result) return
+ if (workspaceKey(store.lastProjectSession[root]?.directory ?? "") === workspaceKey(directory)) {
+ clearLastProjectSession(root)
+ }
+
globalSync.set(
"project",
produce((draft) => {
@@ -1235,8 +1425,18 @@ export default function Layout(props: ParentProps) {
layout.projects.close(directory)
layout.projects.open(root)
- if (params.dir && currentDir() === directory) {
- navigateToProject(root)
+ if (shouldLeave) return
+
+ const nextCurrent = currentDir()
+ const nextKey = workspaceKey(nextCurrent)
+ const project = layout.projects.list().find((item) => item.worktree === root)
+ const dirs = project
+ ? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
+ : [root]
+ const valid = dirs.some((item) => workspaceKey(item) === nextKey)
+
+ if (params.dir && projectRoot(nextCurrent) === root && !valid) {
+ navigateWithSidebarReset(`/${base64Encode(root)}/session`)
}
}
@@ -1339,8 +1539,12 @@ export default function Layout(props: ParentProps) {
})
const handleDelete = () => {
+ const leaveDeletedWorkspace = !!params.dir && workspaceKey(currentDir()) === workspaceKey(props.directory)
+ if (leaveDeletedWorkspace) {
+ navigateWithSidebarReset(`/${base64Encode(props.root)}/session`)
+ }
dialog.close()
- void deleteWorkspace(props.root, props.directory)
+ void deleteWorkspace(props.root, props.directory, leaveDeletedWorkspace)
}
const description = () => {
@@ -1448,26 +1652,42 @@ export default function Layout(props: ParentProps) {
)
}
+ const activeRoute = {
+ session: "",
+ sessionProject: "",
+ }
+
createEffect(
on(
- () => ({ ready: pageReady(), dir: params.dir, id: params.id }),
- (value) => {
- if (!value.ready) return
- const dir = value.dir
- const id = value.id
- if (!dir || !id) return
+ () => [pageReady(), params.dir, params.id, currentProject()?.worktree] as const,
+ ([ready, dir, id]) => {
+ if (!ready || !dir) {
+ activeRoute.session = ""
+ activeRoute.sessionProject = ""
+ return
+ }
+
const directory = decode64(dir)
if (!directory) return
- const at = Date.now()
- setStore("lastProjectSession", projectRoot(directory), { directory, id, at })
- notification.session.markViewed(id)
- const expanded = untrack(() => store.workspaceExpanded[directory])
- if (expanded === false) {
- setStore("workspaceExpanded", directory, true)
+
+ const root = touchProjectRoute() ?? activeProjectRoot(directory)
+
+ if (!id) {
+ activeRoute.session = ""
+ activeRoute.sessionProject = ""
+ return
}
- requestAnimationFrame(() => scrollToSession(id, `${directory}:${id}`))
+
+ const session = `${dir}/${id}`
+ if (session !== activeRoute.session) {
+ activeRoute.session = session
+ activeRoute.sessionProject = syncSessionRoute(directory, id, root)
+ return
+ }
+
+ if (root === activeRoute.sessionProject) return
+ activeRoute.sessionProject = rememberSessionRoute(directory, id, root)
},
- { defer: true },
),
)
@@ -1478,40 +1698,29 @@ export default function Layout(props: ParentProps) {
const loadedSessionDirs = new Set()
- createEffect(() => {
- const project = currentProject()
- const workspaces = workspaceSetting()
- const next = new Set()
- if (!project) {
- loadedSessionDirs.clear()
- return
- }
+ createEffect(
+ on(
+ visibleSessionDirs,
+ (dirs) => {
+ if (dirs.length === 0) {
+ loadedSessionDirs.clear()
+ return
+ }
- if (workspaces) {
- const activeDir = currentDir()
- const dirs = [project.worktree, ...(project.sandboxes ?? [])]
- for (const directory of dirs) {
- const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
- const active = directory === activeDir
- if (!expanded && !active) continue
- next.add(directory)
- }
- }
+ const next = new Set(dirs)
+ for (const directory of next) {
+ if (loadedSessionDirs.has(directory)) continue
+ globalSync.project.loadSessions(directory)
+ }
- if (!workspaces) {
- next.add(project.worktree)
- }
-
- for (const directory of next) {
- if (loadedSessionDirs.has(directory)) continue
- globalSync.project.loadSessions(directory)
- }
-
- loadedSessionDirs.clear()
- for (const directory of next) {
- loadedSessionDirs.add(directory)
- }
- })
+ loadedSessionDirs.clear()
+ for (const directory of next) {
+ loadedSessionDirs.add(directory)
+ }
+ },
+ { defer: true },
+ ),
+ )
function handleDragStart(event: unknown) {
const id = getDraggableId(event)
@@ -1545,14 +1754,11 @@ export default function Layout(props: ParentProps) {
const extra = directory && directory !== local && !dirs.includes(directory) ? directory : undefined
const pending = extra ? WorktreeState.get(extra)?.status === "pending" : false
- const existing = store.workspaceOrder[project.worktree]
- if (!existing) return extra ? [...dirs, extra] : dirs
-
- const merged = syncWorkspaceOrder(local, dirs, existing)
- if (pending && extra) return [local, extra, ...merged.filter((directory) => directory !== local)]
- if (!extra) return merged
- if (pending) return merged
- return [...merged, extra]
+ const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
+ if (pending && extra) return [local, extra, ...ordered.filter((item) => item !== local)]
+ if (!extra) return ordered
+ if (pending) return ordered
+ return [...ordered, extra]
}
const sidebarProject = createMemo(() => {
@@ -1585,7 +1791,11 @@ export default function Layout(props: ParentProps) {
const [item] = result.splice(fromIndex, 1)
if (!item) return
result.splice(toIndex, 0, item)
- setStore("workspaceOrder", project.worktree, result)
+ setStore(
+ "workspaceOrder",
+ project.worktree,
+ result.filter((directory) => workspaceKey(directory) !== workspaceKey(project.worktree)),
+ )
}
function handleWorkspaceDragEnd() {
@@ -1623,10 +1833,9 @@ export default function Layout(props: ParentProps) {
const existing = prev ?? []
const next = existing.filter((item) => {
const id = workspaceKey(item)
- if (id === root) return false
- return id !== key
+ return id !== root && id !== key
})
- return [local, created.directory, ...next]
+ return [created.directory, ...next]
})
globalSync.child(created.directory)
@@ -1692,7 +1901,9 @@ export default function Layout(props: ParentProps) {
setHoverSession,
}
- const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean }) => {
+ const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean; merged?: boolean }) => {
+ const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened()))
+ const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened())
const projectName = createMemo(() => {
const project = panelProps.project
if (!project) return ""
@@ -1718,10 +1929,17 @@ export default function Layout(props: ParentProps) {
return (
{(p) => (
@@ -1767,7 +1985,7 @@ export default function Layout(props: ParentProps) {
}}
aria-label={language.t("common.moreOptions")}
/>
-
+
showEditProjectDialog(p())}>
{language.t("common.edit")}
@@ -1814,20 +2032,14 @@ export default function Layout(props: ParentProps) {
fallback={
<>
- navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)}
>
- navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)}
- >
- {language.t("command.session.new")}
-
-
+ {language.t("command.session.new")}
+
<>
-
- createWorkspace(p())}>
- {language.t("workspace.new")}
-
-
+ createWorkspace(p())}>
+ {language.t("workspace.new")}
+
0 && providers.paid().length === 0),
+ hidden: store.gettingStartedDismissed || !(providers.all().length > 0 && providers.paid().length === 0),
}}
>
-
-
-
{language.t("sidebar.gettingStarted.title")}
-
{language.t("sidebar.gettingStarted.line1")}
-
{language.t("sidebar.gettingStarted.line2")}
+
+
+
+
{language.t("sidebar.gettingStarted.title")}
+
+ {language.t("sidebar.gettingStarted.line1")}
+
+
+ {language.t("sidebar.gettingStarted.line2")}
+
+
+
+
+ {language.t("command.provider.connect")}
+
+ setStore("gettingStartedDismissed", true)}>
+ Not yet
+
+
-
- {language.t("command.provider.connect")}
-
@@ -1924,139 +2136,204 @@ export default function Layout(props: ParentProps) {
}
return (
-
+
-
-
{
- setState("nav", el)
- }}
- onMouseEnter={() => {
- if (navLeave.current === undefined) return
- clearTimeout(navLeave.current)
- navLeave.current = undefined
- }}
- onMouseLeave={() => {
- aim.reset()
- if (!sidebarHovering()) return
+
+
+
+
{
+ setState("nav", el)
+ }}
+ onMouseEnter={() => {
+ disarm()
+ }}
+ onMouseLeave={() => {
+ aim.reset()
+ if (!sidebarHovering()) return
- if (navLeave.current !== undefined) clearTimeout(navLeave.current)
- navLeave.current = window.setTimeout(() => {
- navLeave.current = undefined
- setHoverProject(undefined)
- setState("hoverSession", undefined)
- }, 300)
- }}
- >
-
-
layout.sidebar.opened()}
- aimMove={aim.move}
- projects={() => layout.projects.list()}
- renderProject={(project) => (
-
- )}
- handleDragStart={handleDragStart}
- handleDragEnd={handleDragEnd}
- handleDragOver={handleDragOver}
- openProjectLabel={language.t("command.project.open")}
- openProjectKeybind={() => command.keybind("project.open")}
- onOpenProject={chooseProject}
- renderProjectOverlay={() => (
- layout.projects.list()} activeProject={() => store.activeProject} />
- )}
- settingsLabel={() => language.t("sidebar.settings")}
- settingsKeybind={() => command.keybind("settings.open")}
- onOpenSettings={openSettings}
- helpLabel={() => language.t("sidebar.help")}
- onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
- renderPanel={() => }
- />
-
-
- {(worktree) => (
-
-
+ arm()
+ }}
+ >
+
+
layout.sidebar.opened()}
+ aimMove={aim.move}
+ projects={() => layout.projects.list()}
+ renderProject={(project) => (
+
+ )}
+ handleDragStart={handleDragStart}
+ handleDragEnd={handleDragEnd}
+ handleDragOver={handleDragOver}
+ openProjectLabel={language.t("command.project.open")}
+ openProjectKeybind={() => command.keybind("project.open")}
+ onOpenProject={chooseProject}
+ renderProjectOverlay={() => (
+ layout.projects.list()}
+ activeProject={() => store.activeProject}
+ />
+ )}
+ settingsLabel={() => language.t("sidebar.settings")}
+ settingsKeybind={() => command.keybind("settings.open")}
+ onOpenSettings={openSettings}
+ helpLabel={() => language.t("sidebar.help")}
+ onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
+ renderPanel={() => (
+
+ {(project) => }
+
+ )}
+ />
- )}
-
-
-
-
-
-
-
{
- if (e.target === e.currentTarget) layout.mobileSidebar.hide()
- }}
- />
-
e.stopPropagation()}
- >
- layout.sidebar.opened()}
- aimMove={aim.move}
- projects={() => layout.projects.list()}
- renderProject={(project) => (
-
- )}
- handleDragStart={handleDragStart}
- handleDragEnd={handleDragEnd}
- handleDragOver={handleDragOver}
- openProjectLabel={language.t("command.project.open")}
- openProjectKeybind={() => command.keybind("project.open")}
- onOpenProject={chooseProject}
- renderProjectOverlay={() => (
- layout.projects.list()} activeProject={() => store.activeProject} />
- )}
- settingsLabel={() => language.t("sidebar.settings")}
- settingsKeybind={() => command.keybind("settings.open")}
- onOpenSettings={openSettings}
- helpLabel={() => language.t("sidebar.help")}
- onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
- renderPanel={() => }
- />
-
-
+
+ setSizing(true)}>
+ {
+ setSizing(true)
+ if (sizet !== undefined) clearTimeout(sizet)
+ sizet = window.setTimeout(() => setSizing(false), 120)
+ layout.sidebar.resize(w)
+ }}
+ onCollapse={layout.sidebar.close}
+ />
+
+
+
-
- }>
- {props.children}
-
-
+
+
+
+
{
+ if (e.target === e.currentTarget) layout.mobileSidebar.hide()
+ }}
+ />
+
e.stopPropagation()}
+ >
+ layout.sidebar.opened()}
+ aimMove={aim.move}
+ projects={() => layout.projects.list()}
+ renderProject={(project) => (
+
+ )}
+ handleDragStart={handleDragStart}
+ handleDragEnd={handleDragEnd}
+ handleDragOver={handleDragOver}
+ openProjectLabel={language.t("command.project.open")}
+ openProjectKeybind={() => command.keybind("project.open")}
+ onOpenProject={chooseProject}
+ renderProjectOverlay={() => (
+ layout.projects.list()}
+ activeProject={() => store.activeProject}
+ />
+ )}
+ settingsLabel={() => language.t("sidebar.settings")}
+ settingsKeybind={() => command.keybind("settings.open")}
+ onOpenSettings={openSettings}
+ helpLabel={() => language.t("sidebar.help")}
+ onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
+ renderPanel={() => }
+ />
+
+
+
+
+
+ }>
+ {props.children}
+
+
+
+
+
{
+ disarm()
+ aim.reset()
+ }}
+ onPointerDown={disarm}
+ onMouseLeave={() => {
+ arm()
+ }}
+ >
+
+ {(project) => }
+
+
+
+
+
+
+ {import.meta.env.DEV &&
}
diff --git a/packages/app/src/pages/layout/deep-links.ts b/packages/app/src/pages/layout/deep-links.ts
index 7bdb002a36..5dca421f74 100644
--- a/packages/app/src/pages/layout/deep-links.ts
+++ b/packages/app/src/pages/layout/deep-links.ts
@@ -1,15 +1,17 @@
export const deepLinkEvent = "opencode:deep-link"
-export const parseDeepLink = (input: string) => {
+const parseUrl = (input: string) => {
if (!input.startsWith("opencode://")) return
if (typeof URL.canParse === "function" && !URL.canParse(input)) return
- const url = (() => {
- try {
- return new URL(input)
- } catch {
- return undefined
- }
- })()
+ try {
+ return new URL(input)
+ } catch {
+ return
+ }
+}
+
+export const parseDeepLink = (input: string) => {
+ const url = parseUrl(input)
if (!url) return
if (url.hostname !== "open-project") return
const directory = url.searchParams.get("directory")
@@ -17,9 +19,23 @@ export const parseDeepLink = (input: string) => {
return directory
}
+export const parseNewSessionDeepLink = (input: string) => {
+ const url = parseUrl(input)
+ if (!url) return
+ if (url.hostname !== "new-session") return
+ const directory = url.searchParams.get("directory")
+ if (!directory) return
+ const prompt = url.searchParams.get("prompt") || undefined
+ if (!prompt) return { directory }
+ return { directory, prompt }
+}
+
export const collectOpenProjectDeepLinks = (urls: string[]) =>
urls.map(parseDeepLink).filter((directory): directory is string => !!directory)
+export const collectNewSessionDeepLinks = (urls: string[]) =>
+ urls.map(parseNewSessionDeepLink).filter((link): link is { directory: string; prompt?: string } => !!link)
+
type OpenCodeWindow = Window & {
__OPENCODE__?: {
deepLinks?: string[]
diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts
index 83d8f4748a..d1569dbd9a 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 { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links"
+import {
+ collectNewSessionDeepLinks,
+ collectOpenProjectDeepLinks,
+ drainPendingDeepLinks,
+ parseDeepLink,
+ parseNewSessionDeepLink,
+} from "./deep-links"
import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers"
+import { type Session } from "@opencode-ai/sdk/v2/client"
+import { hasProjectPermissions, latestRootSession } 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", () => {
@@ -42,6 +61,28 @@ describe("layout deep links", () => {
expect(result).toEqual(["/a", "/c"])
})
+ test("parses new-session deep links with optional prompt", () => {
+ expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo")).toEqual({ directory: "/tmp/demo" })
+ expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo&prompt=hello%20world")).toEqual({
+ directory: "/tmp/demo",
+ prompt: "hello world",
+ })
+ })
+
+ test("ignores new-session deep links without directory", () => {
+ expect(parseNewSessionDeepLink("opencode://new-session")).toBeUndefined()
+ expect(parseNewSessionDeepLink("opencode://new-session?directory=")).toBeUndefined()
+ })
+
+ test("collects only valid new-session deep links", () => {
+ const result = collectNewSessionDeepLinks([
+ "opencode://new-session?directory=/a",
+ "opencode://open-project?directory=/b",
+ "opencode://new-session?directory=/c&prompt=ship%20it",
+ ])
+ expect(result).toEqual([{ directory: "/a" }, { directory: "/c", prompt: "ship it" }])
+ })
+
test("drains global deep links once", () => {
const target = {
__OPENCODE__: {
@@ -73,6 +114,84 @@ 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("detects project permissions with a filter", () => {
+ const result = hasProjectPermissions(
+ {
+ root: [{ id: "perm-root" }, { id: "perm-hidden" }],
+ child: [{ id: "perm-child" }],
+ },
+ (item) => item.id === "perm-child",
+ )
+
+ expect(result).toBe(true)
+ })
+
+ test("ignores project permissions filtered out", () => {
+ const result = hasProjectPermissions(
+ {
+ root: [{ id: "perm-root" }],
+ },
+ () => false,
+ )
+
+ expect(result).toBe(false)
+ })
+
+ 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..42315e5893 100644
--- a/packages/app/src/pages/layout/helpers.ts
+++ b/packages/app/src/pages/layout/helpers.ts
@@ -28,6 +28,18 @@ 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 function hasProjectPermissions(
+ request: Record,
+ include: (item: T) => boolean = () => true,
+) {
+ return Object.values(request).some((list) => list?.some(include))
+}
+
export const childMapByParent = (sessions: Session[]) => {
const map = new Map()
for (const session of sessions) {
@@ -62,9 +74,29 @@ export const errorMessage = (err: unknown, fallback: string) => {
return fallback
}
-export const syncWorkspaceOrder = (local: string, dirs: string[], existing?: string[]) => {
- if (!existing) return dirs
- const keep = existing.filter((d) => d !== local && dirs.includes(d))
- const missing = dirs.filter((d) => d !== local && !existing.includes(d))
- return [local, ...missing, ...keep]
+export const effectiveWorkspaceOrder = (local: string, dirs: string[], persisted?: string[]) => {
+ const root = workspaceKey(local)
+ const live = new Map()
+
+ for (const dir of dirs) {
+ const key = workspaceKey(dir)
+ if (key === root) continue
+ if (!live.has(key)) live.set(key, dir)
+ }
+
+ if (!persisted?.length) return [local, ...live.values()]
+
+ const result = [local]
+ for (const dir of persisted) {
+ const key = workspaceKey(dir)
+ if (key === root) continue
+ const match = live.get(key)
+ if (!match) continue
+ result.push(match)
+ live.delete(key)
+ }
+
+ return [...result, ...live.values()]
}
+
+export const syncWorkspaceOrder = effectiveWorkspaceOrder
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx
index 194f75f815..8dc03755e4 100644
--- a/packages/app/src/pages/layout/sidebar-items.tsx
+++ b/packages/app/src/pages/layout/sidebar-items.tsx
@@ -1,31 +1,42 @@
-import { A, useNavigate, useParams } from "@solidjs/router"
-import { useGlobalSync } from "@/context/global-sync"
-import { useLanguage } from "@/context/language"
-import { useLayout, type LocalProject, getAvatarColors } from "@/context/layout"
-import { useNotification } from "@/context/notification"
-import { base64Encode } from "@opencode-ai/util/encode"
+import type { Message, Session, TextPart, UserMessage } from "@opencode-ai/sdk/v2/client"
import { Avatar } from "@opencode-ai/ui/avatar"
-import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { HoverCard } from "@opencode-ai/ui/hover-card"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { MessageNav } from "@opencode-ai/ui/message-nav"
import { Spinner } from "@opencode-ai/ui/spinner"
import { Tooltip } from "@opencode-ai/ui/tooltip"
+import { base64Encode } from "@opencode-ai/util/encode"
import { getFilename } from "@opencode-ai/util/path"
-import { type Message, type Session, type TextPart, type UserMessage } from "@opencode-ai/sdk/v2/client"
-import { For, Match, Show, Switch, createMemo, onCleanup, type Accessor, type JSX } from "solid-js"
+import { A, useNavigate, useParams } from "@solidjs/router"
+import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
+import { useGlobalSync } from "@/context/global-sync"
+import { useLanguage } from "@/context/language"
+import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
+import { useNotification } from "@/context/notification"
+import { usePermission } from "@/context/permission"
import { agentColor } from "@/utils/agent"
+import { sessionPermissionRequest } from "../session/composer/session-request-tree"
+import { hasProjectPermissions } from "./helpers"
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => {
+ const globalSync = useGlobalSync()
const notification = useNotification()
+ const permission = usePermission()
const dirs = createMemo(() => [props.project.worktree, ...(props.project.sandboxes ?? [])])
const unseenCount = createMemo(() =>
dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
)
const hasError = createMemo(() => dirs().some((directory) => notification.project.unseenHasError(directory)))
+ const hasPermissions = createMemo(() =>
+ dirs().some((directory) => {
+ const [store] = globalSync.child(directory, { bootstrap: false })
+ return hasProjectPermissions(store.permission, (item) => !permission.autoResponds(item, directory))
+ }),
+ )
+ const notify = createMemo(() => props.notify && (hasPermissions() || unseenCount() > 0))
const name = createMemo(() => props.project.name || getFilename(props.project.worktree))
return (
@@ -37,15 +48,16 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
}
{...getAvatarColors(props.project.icon?.color)}
class="size-full rounded"
- classList={{ "badge-mask": unseenCount() > 0 && props.notify }}
+ classList={{ "badge-mask": notify() }}
/>
- 0 && props.notify}>
+
@@ -124,13 +136,6 @@ const SessionRow = (props: {
{props.session.title}
-
- {(summary) => (
-
-
-
- )}
-
)
@@ -158,7 +163,6 @@ const SessionHoverPreview = (props: {
gutter={16}
shift={-2}
trigger={props.trigger}
- mount={!props.mobile ? props.nav() : undefined}
open={props.hoverSession() === props.session.id}
onOpenChange={(open) => props.setHoverSession(open ? props.session.id : undefined)}
>
@@ -186,19 +190,15 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
const layout = useLayout()
const language = useLanguage()
const notification = useNotification()
+ const permission = usePermission()
const globalSync = useGlobalSync()
const unseenCount = createMemo(() => notification.session.unseenCount(props.session.id))
const hasError = createMemo(() => notification.session.unseenHasError(props.session.id))
const [sessionStore] = globalSync.child(props.session.directory)
const hasPermissions = createMemo(() => {
- const permissions = sessionStore.permission?.[props.session.id] ?? []
- if (permissions.length > 0) return true
-
- for (const id of props.children.get(props.session.id) ?? []) {
- const childPermissions = sessionStore.permission?.[id] ?? []
- if (childPermissions.length > 0) return true
- }
- return false
+ return !!sessionPermissionRequest(sessionStore.session, sessionStore.permission, props.session.id, (item) => {
+ return !permission.autoResponds(item, props.session.directory)
+ })
})
const isWorking = createMemo(() => {
if (hasPermissions()) return false
@@ -230,7 +230,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
const isActive = createMemo(() => props.session.id === params.id)
- const hoverPrefetch = { current: undefined as ReturnType
| undefined }
+ const hoverPrefetch = {
+ current: undefined as ReturnType | undefined,
+ }
const cancelHoverPrefetch = () => {
if (hoverPrefetch.current === undefined) return
clearTimeout(hoverPrefetch.current)
@@ -299,17 +301,15 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
setHoverSession={props.setHoverSession}
messageLabel={messageLabel}
onMessageSelect={(message) => {
- if (!isActive()) {
+ if (!isActive())
layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id)
- navigate(`${props.slug}/session/${props.session.id}`)
- return
- }
- window.history.replaceState(null, "", `#message-${message.id}`)
- window.dispatchEvent(new HashChangeEvent("hashchange"))
+
+ navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`)
}}
trigger={item}
/>
+
active: Accessor
overlay: Accessor
+ suppressHover: Accessor
dirs: Accessor
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
onProjectMouseLeave: (worktree: string) => void
@@ -71,9 +71,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),
)
@@ -89,6 +91,7 @@ const ProjectTile = (props: {
modal={!props.sidebarHovering()}
onOpenChange={(value) => {
props.setMenu(value)
+ props.setSuppressHover(value)
if (value) props.setOpen(false)
}}
>
@@ -105,24 +108,41 @@ const ProjectTile = (props: {
!props.selected() && !props.active(),
"bg-surface-base-hover border border-border-weak-base": !props.selected() && props.active(),
}}
+ onPointerDown={(event) => {
+ if (!props.overlay()) return
+ if (event.button !== 2 && !(event.button === 0 && event.ctrlKey)) return
+ props.setSuppressHover(true)
+ event.preventDefault()
+ }}
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)}
>
-
+
props.showEditProjectDialog(props.project)}>
{props.language.t("common.edit")}
@@ -179,21 +199,6 @@ const ProjectPreviewPanel = (props: {
{displayName(props.project)}
-
- {
- event.stopPropagation()
- props.setOpen(false)
- props.ctx.closeProject(props.project.worktree)
- }}
- />
-
{props.language.t("sidebar.project.recentSessions")}
@@ -278,16 +283,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 +304,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 +342,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 +352,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 +362,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 +388,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/layout/sidebar-shell.tsx b/packages/app/src/pages/layout/sidebar-shell.tsx
index d813ef3e11..d3070e3749 100644
--- a/packages/app/src/pages/layout/sidebar-shell.tsx
+++ b/packages/app/src/pages/layout/sidebar-shell.tsx
@@ -1,4 +1,4 @@
-import { createMemo, For, Show, type Accessor, type JSX } from "solid-js"
+import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"
import {
DragDropProvider,
DragDropSensors,
@@ -35,10 +35,22 @@ export const SidebarContent = (props: {
}): JSX.Element => {
const expanded = createMemo(() => sidebarExpanded(props.mobile, props.opened()))
const placement = () => (props.mobile ? "bottom" : "right")
+ let panel: HTMLDivElement | undefined
+
+ createEffect(() => {
+ const el = panel
+ if (!el) return
+ if (expanded()) {
+ el.removeAttribute("inert")
+ return
+ }
+ el.setAttribute("inert", "")
+ })
return (
-
+
@@ -100,7 +112,15 @@ export const SidebarContent = (props: {
-
{props.renderPanel()}
+
{
+ panel = el
+ }}
+ classList={{ "flex h-full min-h-0 min-w-0 overflow-hidden": true, "pointer-events-none": !expanded() }}
+ aria-hidden={!expanded()}
+ >
+ {props.renderPanel()}
+
)
}
diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx
index 43d99cf895..c317b9c5ef 100644
--- a/packages/app/src/pages/layout/sidebar-workspace.tsx
+++ b/packages/app/src/pages/layout/sidebar-workspace.tsx
@@ -182,7 +182,7 @@ const WorkspaceActions = (props: {
aria-label={props.language.t("common.moreOptions")}
/>
-
+
{
if (!props.pendingRename()) return
@@ -249,7 +249,7 @@ const WorkspaceSessionList = (props: {
loadMore: () => Promise
language: ReturnType
}): JSX.Element => (
-
+
props.ctx.setScrollContainerRef(el, props.mobile)}
class="size-full flex flex-col py-2 overflow-y-auto no-scrollbar [overflow-anchor:none]"
>
-
+
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index a3f4b7164b..c1552ad024 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1,4 +1,17 @@
-import { onCleanup, Show, Match, Switch, createMemo, createEffect, on, onMount } from "solid-js"
+import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
+import { useDialog } from "@opencode-ai/ui/context/dialog"
+import {
+ onCleanup,
+ Show,
+ Match,
+ Switch,
+ createMemo,
+ createEffect,
+ createComputed,
+ on,
+ onMount,
+ untrack,
+} from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { useLocal } from "@/context/local"
@@ -7,32 +20,242 @@ import { createStore } from "solid-js/store"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Select } from "@opencode-ai/ui/select"
import { createAutoScroll } from "@opencode-ai/ui/hooks"
-import { Mark } from "@opencode-ai/ui/logo"
-
-import { useSync } from "@/context/sync"
-import { useLayout } from "@/context/layout"
-import { checksum, base64Encode } from "@opencode-ai/util/encode"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
-import { useLanguage } from "@/context/language"
-import { useNavigate, useParams } from "@solidjs/router"
-import { UserMessage } from "@opencode-ai/sdk/v2"
-import { useSDK } from "@/context/sdk"
-import { usePrompt } from "@/context/prompt"
+import { Button } from "@opencode-ai/ui/button"
+import { showToast } from "@opencode-ai/ui/toast"
+import { base64Encode, checksum } from "@opencode-ai/util/encode"
+import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
+import { NewSessionView, SessionHeader } from "@/components/session"
import { useComments } from "@/context/comments"
-import { SessionHeader, NewSessionView } from "@/components/session"
-import { same } from "@/utils/same"
-import { createOpenReviewFile } from "@/pages/session/helpers"
-import { createScrollSpy } from "@/pages/session/scroll-spy"
-import { SessionReviewTab, type DiffStyle, type SessionReviewTabProps } from "@/pages/session/review-tab"
-import { TerminalPanel } from "@/pages/session/terminal-panel"
+import { useGlobalSync } from "@/context/global-sync"
+import { useLanguage } from "@/context/language"
+import { useLayout } from "@/context/layout"
+import { usePrompt } from "@/context/prompt"
+import { useSDK } from "@/context/sdk"
+import { useSync } from "@/context/sync"
+import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
+import { createOpenReviewFile, createSizing } from "@/pages/session/helpers"
import { MessageTimeline } from "@/pages/session/message-timeline"
-import { useSessionCommands } from "@/pages/session/use-session-commands"
-import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
+import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
+import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers"
import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs"
import { SessionSidePanel } from "@/pages/session/session-side-panel"
+import { TerminalPanel } from "@/pages/session/terminal-panel"
+import { useSessionCommands } from "@/pages/session/use-session-commands"
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
+import { same } from "@/utils/same"
+import { formatServerError } from "@/utils/server-errors"
+
+const emptyUserMessages: UserMessage[] = []
+
+type SessionHistoryWindowInput = {
+ sessionID: () => string | undefined
+ messagesReady: () => boolean
+ visibleUserMessages: () => UserMessage[]
+ historyMore: () => boolean
+ historyLoading: () => boolean
+ loadMore: (sessionID: string) => Promise
+ userScrolled: () => boolean
+ scroller: () => HTMLDivElement | undefined
+}
+
+/**
+ * Maintains the rendered history window for a session timeline.
+ *
+ * It keeps initial paint bounded to recent turns, reveals cached turns in
+ * small batches while scrolling upward, and prefetches older history near top.
+ */
+function createSessionHistoryWindow(input: SessionHistoryWindowInput) {
+ const turnInit = 10
+ const turnBatch = 8
+ const turnScrollThreshold = 200
+ const turnPrefetchBuffer = 16
+ const prefetchCooldownMs = 400
+ const prefetchNoGrowthLimit = 2
+
+ const [state, setState] = createStore({
+ turnID: undefined as string | undefined,
+ turnStart: 0,
+ prefetchUntil: 0,
+ prefetchNoGrowth: 0,
+ })
+
+ const initialTurnStart = (len: number) => (len > turnInit ? len - turnInit : 0)
+
+ const turnStart = createMemo(() => {
+ const id = input.sessionID()
+ const len = input.visibleUserMessages().length
+ if (!id || len <= 0) return 0
+ if (state.turnID !== id) return initialTurnStart(len)
+ if (state.turnStart <= 0) return 0
+ if (state.turnStart >= len) return initialTurnStart(len)
+ return state.turnStart
+ })
+
+ const setTurnStart = (start: number) => {
+ const id = input.sessionID()
+ const next = start > 0 ? start : 0
+ if (!id) {
+ setState({ turnID: undefined, turnStart: next })
+ return
+ }
+ setState({ turnID: id, turnStart: next })
+ }
+
+ const renderedUserMessages = createMemo(
+ () => {
+ const msgs = input.visibleUserMessages()
+ const start = turnStart()
+ if (start <= 0) return msgs
+ return msgs.slice(start)
+ },
+ emptyUserMessages,
+ {
+ equals: same,
+ },
+ )
+
+ const preserveScroll = (fn: () => void) => {
+ const el = input.scroller()
+ if (!el) {
+ fn()
+ return
+ }
+ const beforeTop = el.scrollTop
+ const beforeHeight = el.scrollHeight
+ fn()
+ requestAnimationFrame(() => {
+ const delta = el.scrollHeight - beforeHeight
+ if (!delta) return
+ el.scrollTop = beforeTop + delta
+ })
+ }
+
+ const backfillTurns = () => {
+ const start = turnStart()
+ if (start <= 0) return
+
+ const next = start - turnBatch
+ const nextStart = next > 0 ? next : 0
+
+ preserveScroll(() => setTurnStart(nextStart))
+ }
+
+ /** Button path: reveal all cached turns, fetch older history, reveal one batch. */
+ const loadAndReveal = async () => {
+ const id = input.sessionID()
+ if (!id) return
+
+ const start = turnStart()
+ const beforeVisible = input.visibleUserMessages().length
+
+ if (start > 0) setTurnStart(0)
+
+ if (!input.historyMore() || input.historyLoading()) return
+
+ await input.loadMore(id)
+ if (input.sessionID() !== id) return
+
+ const afterVisible = input.visibleUserMessages().length
+ const growth = afterVisible - beforeVisible
+ if (state.prefetchNoGrowth) setState("prefetchNoGrowth", 0)
+ if (growth <= 0) return
+ if (turnStart() !== 0) return
+
+ const target = Math.min(afterVisible, Math.max(beforeVisible, renderedUserMessages().length) + turnBatch)
+ const nextStart = Math.max(0, afterVisible - target)
+ preserveScroll(() => setTurnStart(nextStart))
+ }
+
+ /** Scroll/prefetch path: fetch older history from server. */
+ const fetchOlderMessages = async (opts?: { prefetch?: boolean }) => {
+ const id = input.sessionID()
+ if (!id) return
+ if (!input.historyMore() || input.historyLoading()) return
+
+ if (opts?.prefetch) {
+ const now = Date.now()
+ if (state.prefetchUntil > now) return
+ if (state.prefetchNoGrowth >= prefetchNoGrowthLimit) return
+ setState("prefetchUntil", now + prefetchCooldownMs)
+ }
+
+ const start = turnStart()
+ const beforeVisible = input.visibleUserMessages().length
+ const beforeRendered = start <= 0 ? beforeVisible : renderedUserMessages().length
+
+ await input.loadMore(id)
+ if (input.sessionID() !== id) return
+
+ const afterVisible = input.visibleUserMessages().length
+ const growth = afterVisible - beforeVisible
+
+ if (opts?.prefetch) {
+ setState("prefetchNoGrowth", growth > 0 ? 0 : state.prefetchNoGrowth + 1)
+ } else if (growth > 0 && state.prefetchNoGrowth) {
+ setState("prefetchNoGrowth", 0)
+ }
+
+ if (growth <= 0) return
+ if (turnStart() !== start) return
+
+ const reveal = !opts?.prefetch
+ const currentRendered = renderedUserMessages().length
+ const base = Math.max(beforeRendered, currentRendered)
+ const target = reveal ? Math.min(afterVisible, base + turnBatch) : base
+ const nextStart = Math.max(0, afterVisible - target)
+ preserveScroll(() => setTurnStart(nextStart))
+ }
+
+ const onScrollerScroll = () => {
+ if (!input.userScrolled()) return
+ const el = input.scroller()
+ if (!el) return
+ if (el.scrollTop >= turnScrollThreshold) return
+
+ const start = turnStart()
+ if (start > 0) {
+ if (start <= turnPrefetchBuffer) {
+ void fetchOlderMessages({ prefetch: true })
+ }
+ backfillTurns()
+ return
+ }
+
+ void fetchOlderMessages()
+ }
+
+ createEffect(
+ on(
+ input.sessionID,
+ () => {
+ setState({ prefetchUntil: 0, prefetchNoGrowth: 0 })
+ },
+ { defer: true },
+ ),
+ )
+
+ createEffect(
+ on(
+ () => [input.sessionID(), input.messagesReady()] as const,
+ ([id, ready]) => {
+ if (!id || !ready) return
+ setTurnStart(initialTurnStart(input.visibleUserMessages().length))
+ },
+ { defer: true },
+ ),
+ )
+
+ return {
+ turnStart,
+ setTurnStart,
+ renderedUserMessages,
+ loadAndReveal,
+ onScrollerScroll,
+ }
+}
export default function Page() {
+ const globalSync = useGlobalSync()
const layout = useLayout()
const local = useLocal()
const file = useFile()
@@ -44,9 +267,24 @@ export default function Page() {
const sdk = useSDK()
const prompt = usePrompt()
const comments = useComments()
+ const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>()
+
+ createEffect(() => {
+ if (!untrack(() => prompt.ready())) return
+ prompt.ready()
+ untrack(() => {
+ if (params.id || !prompt.ready()) return
+ const text = searchParams.prompt
+ if (!text) return
+ prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
+ setSearchParams({ ...searchParams, prompt: undefined })
+ })
+ })
const [ui, setUi] = createStore({
+ git: false,
pendingMessage: undefined as string | undefined,
+ reviewSnap: false,
scrollGesture: 0,
scroll: {
overflow: false,
@@ -99,6 +337,7 @@ export default function Page() {
)
const isDesktop = createMediaQuery("(min-width: 768px)")
+ const size = createSizing()
const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened())
const desktopSidePanelOpen = createMemo(() => desktopReviewOpen() || desktopFileTreeOpen())
@@ -107,7 +346,7 @@ export default function Page() {
if (desktopReviewOpen()) return `${layout.session.width()}px`
return `calc(100% - ${layout.fileTree.width()}px)`
})
- const centered = createMemo(() => isDesktop() && !desktopSidePanelOpen())
+ const centered = createMemo(() => isDesktop() && !desktopReviewOpen())
function normalizeTab(tab: string) {
if (!tab.startsWith("file://")) return tab
@@ -138,24 +377,6 @@ export default function Page() {
if (path) file.load(path)
})
- createEffect(() => {
- const current = tabs().all()
- if (current.length === 0) return
-
- const next = normalizeTabs(current)
- if (same(current, next)) return
-
- tabs().setAll(next)
-
- const active = tabs().active()
- if (!active) return
- if (!active.startsWith("file://")) return
-
- const normalized = normalizeTab(active)
- if (active === normalized) return
- tabs().setActive(normalized)
- })
-
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length))
@@ -178,7 +399,6 @@ export default function Page() {
return sync.session.history.loading(id)
})
- const emptyUserMessages: UserMessage[] = []
const userMessages = createMemo(
() => messages().filter((m) => m.role === "user") as UserMessage[],
emptyUserMessages,
@@ -203,37 +423,61 @@ export default function Page() {
() => {
const msg = lastUserMessage()
if (!msg) return
- if (msg.agent) local.agent.set(msg.agent)
- if (msg.model) local.model.set(msg.model)
+ syncSessionModel(local, msg)
},
),
)
+ createEffect(
+ on(
+ () => ({ dir: params.dir, id: params.id }),
+ (next, prev) => {
+ if (!prev) return
+ if (next.dir === prev.dir && next.id === prev.id) return
+ if (prev.id) sync.session.evict(prev.id, prev.dir)
+ if (!next.id) resetSessionModel(local)
+ },
+ { defer: true },
+ ),
+ )
+
const [store, setStore] = createStore({
messageId: undefined as string | undefined,
- turnStart: 0,
mobileTab: "session" as "session" | "changes",
changes: "session" as "session" | "turn",
newSessionWorktree: "main",
+ deferRender: false,
})
+ createComputed((prev) => {
+ const key = sessionKey()
+ if (key !== prev) {
+ setStore("deferRender", true)
+ requestAnimationFrame(() => {
+ setTimeout(() => setStore("deferRender", false), 0)
+ })
+ }
+ return key
+ }, sessionKey())
+
+ let reviewFrame: number | undefined
+
+ createComputed((prev) => {
+ const open = desktopReviewOpen()
+ if (prev === undefined || prev === open) return open
+
+ if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame)
+ setUi("reviewSnap", true)
+ reviewFrame = requestAnimationFrame(() => {
+ reviewFrame = undefined
+ setUi("reviewSnap", false)
+ })
+ return open
+ }, desktopReviewOpen())
+
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs()))
- const renderedUserMessages = createMemo(
- () => {
- const msgs = visibleUserMessages()
- const start = store.turnStart
- if (start <= 0) return msgs
- if (start >= msgs.length) return emptyUserMessages
- return msgs.slice(start)
- },
- emptyUserMessages,
- {
- equals: same,
- },
- )
-
const newSessionWorktree = createMemo(() => {
if (store.newSessionWorktree === "create") return "create"
const project = sync.project
@@ -241,25 +485,55 @@ export default function Page() {
return "main"
})
- const activeMessage = createMemo(() => {
- if (!store.messageId) return lastUserMessage()
- const found = visibleUserMessages()?.find((m) => m.id === store.messageId)
- return found ?? lastUserMessage()
- })
const setActiveMessage = (message: UserMessage | undefined) => {
+ messageMark = scrollMark
setStore("messageId", message?.id)
}
+ const anchor = (id: string) => `message-${id}`
+
+ const cursor = () => {
+ const root = scroller
+ if (!root) return store.messageId
+
+ const box = root.getBoundingClientRect()
+ const line = box.top + 100
+ const list = [...root.querySelectorAll("[data-message-id]")]
+ .map((el) => {
+ const id = el.dataset.messageId
+ if (!id) return
+
+ const rect = el.getBoundingClientRect()
+ return { id, top: rect.top, bottom: rect.bottom }
+ })
+ .filter((item): item is { id: string; top: number; bottom: number } => !!item)
+
+ const shown = list.filter((item) => item.bottom > box.top && item.top < box.bottom)
+ const hit = shown.find((item) => item.top <= line && item.bottom >= line)
+ if (hit) return hit.id
+
+ const near = [...shown].sort((a, b) => {
+ const da = Math.abs(a.top - line)
+ const db = Math.abs(b.top - line)
+ if (da !== db) return da - db
+ return a.top - b.top
+ })[0]
+ if (near) return near.id
+
+ return list.filter((item) => item.top <= line).at(-1)?.id ?? list[0]?.id ?? store.messageId
+ }
+
function navigateMessageByOffset(offset: number) {
const msgs = visibleUserMessages()
if (msgs.length === 0) return
- const current = activeMessage()
- const currentIndex = current ? msgs.findIndex((m) => m.id === current.id) : -1
- const targetIndex = currentIndex === -1 ? (offset > 0 ? 0 : msgs.length - 1) : currentIndex + offset
- if (targetIndex < 0 || targetIndex >= msgs.length) return
+ const current = store.messageId && messageMark === scrollMark ? store.messageId : cursor()
+ const base = current ? msgs.findIndex((m) => m.id === current) : msgs.length
+ const currentIndex = base === -1 ? msgs.length : base
+ const targetIndex = currentIndex + offset
+ if (targetIndex < 0 || targetIndex > msgs.length) return
- if (targetIndex === msgs.length - 1) {
+ if (targetIndex === msgs.length) {
resumeScroll()
return
}
@@ -274,12 +548,60 @@ export default function Page() {
if (!hasReview()) return true
return sync.data.session_diff[id] !== undefined
})
+ const reviewEmptyKey = createMemo(() => {
+ const project = sync.project
+ if (project && !project.vcs) return "session.review.noVcs"
+ if (sync.data.config.snapshot === false) return "session.review.noSnapshot"
+ return "session.review.empty"
+ })
+
+ function upsert(next: Project) {
+ const list = globalSync.data.project
+ sync.set("project", next.id)
+ const idx = list.findIndex((item) => item.id === next.id)
+ if (idx >= 0) {
+ globalSync.set(
+ "project",
+ list.map((item, i) => (i === idx ? { ...item, ...next } : item)),
+ )
+ return
+ }
+ const at = list.findIndex((item) => item.id > next.id)
+ if (at >= 0) {
+ globalSync.set("project", [...list.slice(0, at), next, ...list.slice(at)])
+ return
+ }
+ globalSync.set("project", [...list, next])
+ }
+
+ function initGit() {
+ if (ui.git) return
+ setUi("git", true)
+ void sdk.client.project
+ .initGit()
+ .then((x) => {
+ if (!x.data) return
+ upsert(x.data)
+ })
+ .catch((err) => {
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: formatServerError(err, language.t),
+ })
+ })
+ .finally(() => {
+ setUi("git", false)
+ })
+ }
let inputRef!: HTMLDivElement
let promptDock: HTMLDivElement | undefined
let dockHeight = 0
let scroller: HTMLDivElement | undefined
let content: HTMLDivElement | undefined
+ let scrollMark = 0
+ let messageMark = 0
const scrollGestureWindowMs = 250
@@ -296,13 +618,15 @@ export default function Page() {
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
- createEffect(() => {
- sdk.directory
- const id = params.id
- if (!id) return
- void sync.session.sync(id)
- void sync.session.todo(id)
- })
+ createEffect(
+ on([() => sdk.directory, () => params.id] as const, ([, id]) => {
+ if (!id) return
+ untrack(() => {
+ void sync.session.sync(id)
+ void sync.session.todo(id)
+ })
+ }),
+ )
createEffect(
on(
@@ -322,6 +646,7 @@ export default function Page() {
() => {
setStore("messageId", undefined)
setStore("changes", "session")
+ setUi("pendingMessage", undefined)
},
{ defer: true },
),
@@ -373,11 +698,58 @@ export default function Page() {
})
}
+ const updateCommentInContext = (input: {
+ id: string
+ file: string
+ selection: SelectedLineRange
+ comment: string
+ preview?: string
+ }) => {
+ comments.update(input.file, input.id, input.comment)
+ prompt.context.updateComment(input.file, input.id, {
+ comment: input.comment,
+ ...(input.preview ? { preview: input.preview } : {}),
+ })
+ }
+
+ const removeCommentFromContext = (input: { id: string; file: string }) => {
+ comments.remove(input.file, input.id)
+ prompt.context.removeComment(input.file, input.id)
+ }
+
+ const reviewCommentActions = createMemo(() => ({
+ moreLabel: language.t("common.moreOptions"),
+ editLabel: language.t("common.edit"),
+ deleteLabel: language.t("common.delete"),
+ saveLabel: language.t("common.save"),
+ }))
+
+ const isEditableTarget = (target: EventTarget | null | undefined) => {
+ if (!(target instanceof HTMLElement)) return false
+ return /^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test(target.tagName) || target.isContentEditable
+ }
+
+ const deepActiveElement = () => {
+ let current: Element | null = document.activeElement
+ while (current instanceof HTMLElement && current.shadowRoot?.activeElement) {
+ current = current.shadowRoot.activeElement
+ }
+ return current instanceof HTMLElement ? current : undefined
+ }
+
const handleKeyDown = (event: KeyboardEvent) => {
- const activeElement = document.activeElement as HTMLElement | undefined
+ const path = event.composedPath()
+ const target = path.find((item): item is HTMLElement => item instanceof HTMLElement)
+ const activeElement = deepActiveElement()
+
+ const protectedTarget = path.some(
+ (item) => item instanceof HTMLElement && item.closest("[data-prevent-autofocus]") !== null,
+ )
+ if (protectedTarget || isEditableTarget(target)) return
+
if (activeElement) {
const isProtected = activeElement.closest("[data-prevent-autofocus]")
- const isInput = /^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test(activeElement.tagName) || activeElement.isContentEditable
+ const isInput = isEditableTarget(activeElement)
if (isProtected || isInput) return
}
if (dialog.active) return
@@ -410,7 +782,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)
@@ -425,7 +797,11 @@ export default function Page() {
on(
sessionKey,
() => {
- setTree({ reviewScroll: undefined, pendingDiff: undefined, activeDiff: undefined })
+ setTree({
+ reviewScroll: undefined,
+ pendingDiff: undefined,
+ activeDiff: undefined,
+ })
},
{ defer: true },
),
@@ -448,28 +824,35 @@ export default function Page() {
showAllFiles,
tabForPath: file.tab,
openTab: tabs().open,
+ setActive: tabs().setActive,
loadFile: file.load,
})
const changesOptions = ["session", "turn"] as const
const changesOptionsList = [...changesOptions]
- const changesTitle = () => (
-