Compare commits
168
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33637f3e35 | ||
|
|
a44f78c34a | ||
|
|
a5d727e7f9 | ||
|
|
7b5b665b4a | ||
|
|
b5515dd2f7 | ||
|
|
d16e5b98dc | ||
|
|
9dbf3a2042 | ||
|
|
d1938a472d | ||
|
|
c0483affa6 | ||
|
|
ae0f69e1fa | ||
|
|
90270c615d | ||
|
|
6b7e6bde4d | ||
|
|
b15fb21191 | ||
|
|
c8866e60ba | ||
|
|
f5eade1d2b | ||
|
|
438610aa64 | ||
|
|
c4c0b23bff | ||
|
|
38704acacd | ||
|
|
4d968ebd64 | ||
|
|
b88e8e0e0b | ||
|
|
3ee1653f40 | ||
|
|
fcd733e3d6 | ||
|
|
cec16dfe95 | ||
|
|
114eb42444 | ||
|
|
e1e18c7abd | ||
|
|
971bd30516 | ||
|
|
2a2082233d | ||
|
|
267d2c82de | ||
|
|
0b8c1f1f7d | ||
|
|
2eb1d4cb9a | ||
|
|
d2a8f44c22 | ||
|
|
1f1f36aac1 | ||
|
|
7f851da15e | ||
|
|
a3bdb974b3 | ||
|
|
46d678fce9 | ||
|
|
1f2348c1ef | ||
|
|
f347194e31 | ||
|
|
7ff2710ce3 | ||
|
|
c12ce2ffff | ||
|
|
a94f564ff0 | ||
|
|
6ef3af73df | ||
|
|
e5ae6c51b0 | ||
|
|
9d76ef6c66 | ||
|
|
e49e781cb8 | ||
|
|
78cea89e0e | ||
|
|
3dc10a1c16 | ||
|
|
157920b2fb | ||
|
|
967313234a | ||
|
|
dfa0281117 | ||
|
|
4a94096994 | ||
|
|
3407ded9d0 | ||
|
|
a325c9af8f | ||
|
|
dc8c011510 | ||
|
|
1f108bc401 | ||
|
|
6b3118883c | ||
|
|
0da8af8a28 | ||
|
|
2a4ed49551 | ||
|
|
7528419172 | ||
|
|
8c739b4a7d | ||
|
|
f2100dcfd8 | ||
|
|
b0b88f6792 | ||
|
|
e9a7c71141 | ||
|
|
4205fbd2aa | ||
|
|
fc52e4b2d3 | ||
|
|
9a6bfeb782 | ||
|
|
fa119423ec | ||
|
|
bf442a50c0 | ||
|
|
09e1b98bc6 | ||
|
|
37d42595cf | ||
|
|
adabad19f1 | ||
|
|
7a74be3b47 | ||
|
|
c95febb1d5 | ||
|
|
9736fce8fc | ||
|
|
05d77b7d47 | ||
|
|
8c484a05b8 | ||
|
|
a0b3bbffd5 | ||
|
|
270d084cb1 | ||
|
|
9312867565 | ||
|
|
7e6a007c35 | ||
|
|
5745ee87ba | ||
|
|
08f056d412 | ||
|
|
96ca0de3bc | ||
|
|
b4d0090e00 | ||
|
|
05ac0a73e1 | ||
|
|
7453e78b35 | ||
|
|
bb8a1718a6 | ||
|
|
6b021658ad | ||
|
|
799b2623cb | ||
|
|
fce811b52f | ||
|
|
aae75b3cfb | ||
|
|
392a6d993f | ||
|
|
c4ea11fef3 | ||
|
|
b8337cddc4 | ||
|
|
444178e079 | ||
|
|
4551282a4b | ||
|
|
9d29d692c6 | ||
|
|
1172fa418e | ||
|
|
b368181ac9 | ||
|
|
7afa48b4ef | ||
|
|
45191ad144 | ||
|
|
2869922696 | ||
|
|
e48c1ccf07 | ||
|
|
5e5823ed85 | ||
|
|
de2bc25677 | ||
|
|
79b5ce58e9 | ||
|
|
088a81c116 | ||
|
|
d848c9b6a3 | ||
|
|
561f9f5f05 | ||
|
|
3c6c74457d | ||
|
|
fc6e7934bd | ||
|
|
d7500b25b8 | ||
|
|
5d5f2cfee6 | ||
|
|
1172ebe697 | ||
|
|
d00d98d56a | ||
|
|
6fc5506293 | ||
|
|
76b60f3779 | ||
|
|
6af7ddf03b | ||
|
|
0b3fb5d460 | ||
|
|
a487f11a30 | ||
|
|
637059a515 | ||
|
|
fa559b0385 | ||
|
|
814c1d398c | ||
|
|
da40ab7b3d | ||
|
|
e718263778 | ||
|
|
3af12c53c4 | ||
|
|
29ddd55088 | ||
|
|
2c00eb60bd | ||
|
|
2a87860c06 | ||
|
|
68cf011fd3 | ||
|
|
f8cfb697bd | ||
|
|
c6d8e7624d | ||
|
|
0d0d0578eb | ||
|
|
cc02476ea5 | ||
|
|
5190589632 | ||
|
|
c92913e962 | ||
|
|
082f0cc127 | ||
|
|
2cee947671 | ||
|
|
e27d3d5d40 | ||
|
|
32417774c4 | ||
|
|
36197f5ff8 | ||
|
|
3d379c20c4 | ||
|
|
06f25c78f6 | ||
|
|
1a0639e5b8 | ||
|
|
1af3e9e557 | ||
|
|
a292eddeb5 | ||
|
|
79254c1020 | ||
|
|
ef7f222d80 | ||
|
|
888b123387 | ||
|
|
13cabae29f | ||
|
|
659068942e | ||
|
|
3201a7d34b | ||
|
|
de796d9a00 | ||
|
|
a592bd9684 | ||
|
|
744059a00f | ||
|
|
fb6d201ee0 | ||
|
|
cda2af2589 | ||
|
|
eda71373b0 | ||
|
|
cf5cfb48cd | ||
|
|
ae190038f8 | ||
|
|
0269f39a17 | ||
|
|
0a91196919 | ||
|
|
284251ad66 | ||
|
|
34495a70d5 | ||
|
|
ad5f0816a3 | ||
|
|
24c63914bf | ||
|
|
8f2d8dd47a | ||
|
|
3b5b21a91e | ||
|
|
8e96447960 |
@@ -11,10 +11,25 @@ runs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-bun-
|
${{ 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
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
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
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "0 * * * *"
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: beta-sync
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
@@ -31,7 +27,11 @@ jobs:
|
|||||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
|
- name: Install OpenCode
|
||||||
|
run: bun i -g opencode-ai
|
||||||
|
|
||||||
- name: Sync beta branch
|
- name: Sync beta branch
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
||||||
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
run: bun script/beta.ts
|
run: bun script/beta.ts
|
||||||
|
|||||||
@@ -65,6 +65,15 @@ jobs:
|
|||||||
body: closeMessage,
|
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) {
|
if (isPR) {
|
||||||
await github.rest.pulls.update({
|
await github.rest.pulls.update({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|||||||
@@ -47,12 +47,14 @@ jobs:
|
|||||||
echo "EOF"
|
echo "EOF"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$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
|
- name: Sync locale docs with OpenCode
|
||||||
if: steps.changes.outputs.has_changes == 'true'
|
if: steps.changes.outputs.has_changes == 'true'
|
||||||
uses: sst/opencode/github@latest
|
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
|
||||||
OPENCODE_CONFIG_CONTENT: |
|
OPENCODE_CONFIG_CONTENT: |
|
||||||
{
|
{
|
||||||
"permission": {
|
"permission": {
|
||||||
@@ -65,9 +67,9 @@ jobs:
|
|||||||
"packages/web/src/content/docs/*/*.mdx": "allow",
|
"packages/web/src/content/docs/*/*.mdx": "allow",
|
||||||
".opencode": "allow",
|
".opencode": "allow",
|
||||||
".opencode/agent": "allow",
|
".opencode/agent": "allow",
|
||||||
".opencode/agent/glossary": "allow",
|
".opencode/glossary": "allow",
|
||||||
".opencode/agent/translator.md": "allow",
|
".opencode/agent/translator.md": "allow",
|
||||||
".opencode/agent/glossary/*.md": "allow"
|
".opencode/glossary/*.md": "allow"
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"*": "deny",
|
"*": "deny",
|
||||||
@@ -76,7 +78,7 @@ jobs:
|
|||||||
"glob": {
|
"glob": {
|
||||||
"*": "deny",
|
"*": "deny",
|
||||||
"packages/web/src/content/docs*": "allow",
|
"packages/web/src/content/docs*": "allow",
|
||||||
".opencode/agent/glossary*": "allow"
|
".opencode/glossary*": "allow"
|
||||||
},
|
},
|
||||||
"task": {
|
"task": {
|
||||||
"*": "deny",
|
"*": "deny",
|
||||||
@@ -90,17 +92,14 @@ jobs:
|
|||||||
"read": {
|
"read": {
|
||||||
"*": "deny",
|
"*": "deny",
|
||||||
".opencode/agent/translator.md": "allow",
|
".opencode/agent/translator.md": "allow",
|
||||||
".opencode/agent/glossary/*.md": "allow"
|
".opencode/glossary/*.md": "allow"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
with:
|
run: |
|
||||||
model: opencode/gpt-5.3-codex
|
opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
|
||||||
agent: docs
|
|
||||||
use_github_token: true
|
|
||||||
prompt: |
|
|
||||||
Update localized docs to match the latest English docs changes.
|
Update localized docs to match the latest English docs changes.
|
||||||
|
|
||||||
Changed English doc files:
|
Changed English doc files:
|
||||||
@@ -118,6 +117,7 @@ jobs:
|
|||||||
7. Keep locale docs structure aligned with their corresponding English pages.
|
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.
|
8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||||
9. If no locale updates are needed, make no changes.
|
9. If no locale updates are needed, make no changes.
|
||||||
|
EOF
|
||||||
|
|
||||||
- name: Commit and push locale docs updates
|
- name: Commit and push locale docs updates
|
||||||
if: steps.changes.outputs.has_changes == 'true'
|
if: steps.changes.outputs.has_changes == 'true'
|
||||||
|
|||||||
@@ -108,11 +108,11 @@ jobs:
|
|||||||
|
|
||||||
await removeLabel('needs:title');
|
await removeLabel('needs:title');
|
||||||
|
|
||||||
// Step 2: Check for linked issue (skip for docs/refactor PRs)
|
// Step 2: Check for linked issue (skip for docs/refactor/feat PRs)
|
||||||
const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
||||||
if (skipIssueCheck) {
|
if (skipIssueCheck) {
|
||||||
await removeLabel('needs:issue');
|
await removeLabel('needs:issue');
|
||||||
console.log('Skipping issue check for docs/refactor PR');
|
console.log('Skipping issue check for docs/refactor/feat PR');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const query = `
|
const query = `
|
||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
|
|
||||||
const body = pr.body || '';
|
const body = pr.body || '';
|
||||||
const title = pr.title;
|
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 = [];
|
const issues = [];
|
||||||
|
|
||||||
@@ -225,8 +225,8 @@ jobs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check: issue reference (skip for docs/refactor)
|
// Check: issue reference (skip for docs/refactor/feat)
|
||||||
if (!isDocsOrRefactor && hasIssueSection) {
|
if (!isDocsRefactorOrFeat && hasIssueSection) {
|
||||||
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
|
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
|
||||||
const issueContent = issueMatch ? issueMatch[1].trim() : '';
|
const issueContent = issueMatch ? issueMatch[1].trim() : '';
|
||||||
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
|
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
|
||||||
|
|||||||
@@ -8,8 +8,16 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
unit:
|
unit:
|
||||||
name: unit (linux)
|
name: unit (${{ matrix.settings.name }})
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
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:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -42,15 +42,17 @@ jobs:
|
|||||||
throw error;
|
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();
|
const denounced = new Map();
|
||||||
for (const line of content.split('\n')) {
|
for (const line of content.split('\n')) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
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;
|
if (!rest) continue;
|
||||||
|
|
||||||
const spaceIdx = rest.indexOf(' ');
|
const spaceIdx = rest.indexOf(' ');
|
||||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||||
@@ -65,16 +67,17 @@ jobs:
|
|||||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||||
if (!username) continue;
|
if (!username) continue;
|
||||||
|
|
||||||
|
if (isDenounced) {
|
||||||
denounced.set(username.toLowerCase(), reason);
|
denounced.set(username.toLowerCase(), reason);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vouched.add(username.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the author is denounced
|
// Check if the author is denounced
|
||||||
const reason = denounced.get(author.toLowerCase());
|
const reason = denounced.get(author.toLowerCase());
|
||||||
if (reason === undefined) {
|
if (reason !== undefined) {
|
||||||
core.info(`User ${author} is not denounced. Allowing issue.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author is denounced — close the issue
|
// Author is denounced — close the issue
|
||||||
const body = 'This issue has been automatically closed.';
|
const body = 'This issue has been automatically closed.';
|
||||||
|
|
||||||
@@ -94,3 +97,20 @@ jobs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
|
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: ['Vouched'],
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -42,15 +43,17 @@ jobs:
|
|||||||
throw error;
|
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();
|
const denounced = new Map();
|
||||||
for (const line of content.split('\n')) {
|
for (const line of content.split('\n')) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
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;
|
if (!rest) continue;
|
||||||
|
|
||||||
const spaceIdx = rest.indexOf(' ');
|
const spaceIdx = rest.indexOf(' ');
|
||||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||||
@@ -65,16 +68,17 @@ jobs:
|
|||||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||||
if (!username) continue;
|
if (!username) continue;
|
||||||
|
|
||||||
|
if (isDenounced) {
|
||||||
denounced.set(username.toLowerCase(), reason);
|
denounced.set(username.toLowerCase(), reason);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vouched.add(username.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the author is denounced
|
// Check if the author is denounced
|
||||||
const reason = denounced.get(author.toLowerCase());
|
const reason = denounced.get(author.toLowerCase());
|
||||||
if (reason === undefined) {
|
if (reason !== undefined) {
|
||||||
core.info(`User ${author} is not denounced. Allowing PR.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Author is denounced — close the PR
|
// Author is denounced — close the PR
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
@@ -91,3 +95,20 @@ jobs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
core.info(`Closed PR #${prNumber} from denounced user ${author}`);
|
core.info(`Closed PR #${prNumber} from denounced user ${author}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
labels: ['Vouched'],
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(`Added vouched label to PR #${prNumber} from ${author}`);
|
||||||
|
|||||||
@@ -33,5 +33,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
issue-id: ${{ github.event.issue.number }}
|
issue-id: ${{ github.event.issue.number }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
|
roles: admin,maintain
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: Translate content for a specified locale while preserving technical terms
|
description: Translate content for a specified locale while preserving technical terms
|
||||||
mode: subagent
|
mode: subagent
|
||||||
model: opencode/gemini-3.1-pro
|
model: opencode/gemini-3-pro
|
||||||
---
|
---
|
||||||
|
|
||||||
You are a professional translator and localization specialist.
|
You are a professional translator and localization specialist.
|
||||||
@@ -13,7 +13,7 @@ Requirements:
|
|||||||
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
- 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.
|
- 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 preserve every term listed in the Do-Not-Translate glossary below.
|
||||||
- Also apply locale-specific guidance from `.opencode/agent/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
||||||
- Do not modify fenced code blocks.
|
- Do not modify fenced code blocks.
|
||||||
- Output ONLY the translation (no commentary).
|
- Output ONLY the translation (no commentary).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Fixed typecheck error by reverting key name from 'session.new.worktree.startup' back to 'session.new.workspace.startup' in packages/console/app/src/i18n/tr.ts.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Applied minor linguistic polishes to Turkish translations in packages/console/app/src/i18n/tr.ts. PR created at https://github.com/anomalyco/opencode/pull/15468
|
||||||
@@ -111,3 +111,7 @@ const table = sqliteTable("session", {
|
|||||||
- Avoid mocks as much as possible
|
- Avoid mocks as much as possible
|
||||||
- Test actual implementation, do not duplicate logic into tests
|
- Test actual implementation, do not duplicate logic into tests
|
||||||
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
|
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
|
||||||
|
|
||||||
|
## Type Checking
|
||||||
|
|
||||||
|
- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.
|
||||||
|
|||||||
+3
-1
@@ -27,13 +27,15 @@
|
|||||||
<a href="README.ja.md">日本語</a> |
|
<a href="README.ja.md">日本語</a> |
|
||||||
<a href="README.pl.md">Polski</a> |
|
<a href="README.pl.md">Polski</a> |
|
||||||
<a href="README.ru.md">Русский</a> |
|
<a href="README.ru.md">Русский</a> |
|
||||||
|
<a href="README.bs.md">Bosanski</a> |
|
||||||
<a href="README.ar.md">العربية</a> |
|
<a href="README.ar.md">العربية</a> |
|
||||||
<a href="README.no.md">Norsk</a> |
|
<a href="README.no.md">Norsk</a> |
|
||||||
<a href="README.br.md">Português (Brasil)</a> |
|
<a href="README.br.md">Português (Brasil)</a> |
|
||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -34,7 +34,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -34,7 +34,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://opencode.ai">
|
||||||
|
<picture>
|
||||||
|
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||||
|
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||||
|
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">Ο πράκτορας τεχνητής νοημοσύνης ανοικτού κώδικα για προγραμματισμό.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||||
|
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||||
|
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="README.md">English</a> |
|
||||||
|
<a href="README.zh.md">简体中文</a> |
|
||||||
|
<a href="README.zht.md">繁體中文</a> |
|
||||||
|
<a href="README.ko.md">한국어</a> |
|
||||||
|
<a href="README.de.md">Deutsch</a> |
|
||||||
|
<a href="README.es.md">Español</a> |
|
||||||
|
<a href="README.fr.md">Français</a> |
|
||||||
|
<a href="README.it.md">Italiano</a> |
|
||||||
|
<a href="README.da.md">Dansk</a> |
|
||||||
|
<a href="README.ja.md">日本語</a> |
|
||||||
|
<a href="README.pl.md">Polski</a> |
|
||||||
|
<a href="README.ru.md">Русский</a> |
|
||||||
|
<a href="README.bs.md">Bosanski</a> |
|
||||||
|
<a href="README.ar.md">العربية</a> |
|
||||||
|
<a href="README.no.md">Norsk</a> |
|
||||||
|
<a href="README.br.md">Português (Brasil)</a> |
|
||||||
|
<a href="README.th.md">ไทย</a> |
|
||||||
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
|
<a href="README.uk.md">Українська</a> |
|
||||||
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[](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)
|
||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -34,7 +34,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
+2
-1
@@ -33,7 +33,8 @@
|
|||||||
<a href="README.th.md">ไทย</a> |
|
<a href="README.th.md">ไทย</a> |
|
||||||
<a href="README.tr.md">Türkçe</a> |
|
<a href="README.tr.md">Türkçe</a> |
|
||||||
<a href="README.uk.md">Українська</a> |
|
<a href="README.uk.md">Українська</a> |
|
||||||
<a href="README.bn.md">বাংলা</a>
|
<a href="README.bn.md">বাংলা</a> |
|
||||||
|
<a href="README.gr.md">Ελληνικά</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://opencode.ai)
|
[](https://opencode.ai)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -376,7 +376,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.90.10",
|
"@hey-api/openapi-ts": "0.90.10",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -418,9 +418,30 @@
|
|||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/storybook": {
|
||||||
|
"name": "@opencode-ai/storybook",
|
||||||
|
"devDependencies": {
|
||||||
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
"@solidjs/meta": "catalog:",
|
||||||
|
"@storybook/addon-a11y": "^10.2.10",
|
||||||
|
"@storybook/addon-docs": "^10.2.10",
|
||||||
|
"@storybook/addon-links": "^10.2.10",
|
||||||
|
"@storybook/addon-onboarding": "^10.2.10",
|
||||||
|
"@storybook/addon-vitest": "^10.2.10",
|
||||||
|
"@tsconfig/node22": "catalog:",
|
||||||
|
"@types/node": "catalog:",
|
||||||
|
"@types/react": "18.0.25",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"solid-js": "catalog:",
|
||||||
|
"storybook": "^10.2.10",
|
||||||
|
"storybook-solidjs-vite": "^10.0.9",
|
||||||
|
"typescript": "catalog:",
|
||||||
|
"vite": "catalog:",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -462,7 +483,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -473,7 +494,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -524,7 +545,7 @@
|
|||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@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",
|
"@playwright/test": "1.51.0",
|
||||||
"@solid-primitives/storage": "4.3.3",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@solidjs/meta": "0.29.4",
|
"@solidjs/meta": "0.29.4",
|
||||||
@@ -1136,6 +1157,8 @@
|
|||||||
|
|
||||||
"@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
|
"@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/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=="],
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
@@ -1208,6 +1231,8 @@
|
|||||||
|
|
||||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
"@mdx-js/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=="],
|
"@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=="],
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
|
||||||
@@ -1302,6 +1327,8 @@
|
|||||||
|
|
||||||
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
|
"@opencode-ai/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/ui": ["@opencode-ai/ui@workspace:packages/ui"],
|
||||||
|
|
||||||
"@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"],
|
"@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"],
|
||||||
@@ -1442,7 +1469,9 @@
|
|||||||
|
|
||||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
"@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=="],
|
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||||
|
|
||||||
@@ -1774,6 +1803,26 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
|
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="],
|
||||||
|
|
||||||
|
"@storybook/addon-docs": ["@storybook/addon-docs@10.2.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="],
|
||||||
|
|
||||||
|
"@storybook/addon-links": ["@storybook/addon-links@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" }, "optionalPeers": ["react"] }, "sha512-oo9Xx4/2OVJtptXKpqH4ySri7ZuBdiSOXlZVGejEfLa0Jeajlh/KIlREpGvzPPOqUVT7dSddWzBjJmJUyQC3ew=="],
|
||||||
|
|
||||||
|
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.10", "", { "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-DkzZQTXHp99SpHMIQ5plbbHcs4EWVzWhLXlW+icA8sBlKo5Bwj540YcOApKbqB0m/OzWprsznwN7Kv4vfvHu4w=="],
|
||||||
|
|
||||||
|
"@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.10", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-U2oHw+Ar+Xd06wDTB74VlujhIIW89OHThpJjwgqgM6NWrOC/XLllJ53ILFDyREBkMwpBD7gJQIoQpLEcKBIEhw=="],
|
||||||
|
|
||||||
|
"@storybook/builder-vite": ["@storybook/builder-vite@10.2.10", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="],
|
||||||
|
|
||||||
|
"@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="],
|
||||||
|
|
||||||
|
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
|
||||||
|
|
||||||
|
"@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="],
|
||||||
|
|
||||||
|
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="],
|
||||||
|
|
||||||
"@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
|
"@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=="],
|
"@swc/helpers": ["@swc/helpers@0.5.18", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="],
|
||||||
@@ -1866,6 +1915,12 @@
|
|||||||
|
|
||||||
"@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
|
"@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=="],
|
"@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=="],
|
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||||
@@ -1876,6 +1931,8 @@
|
|||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@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__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=="],
|
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||||
@@ -2010,7 +2067,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=="],
|
"@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=="],
|
"@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 +2077,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/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=="],
|
"@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
|
||||||
|
|
||||||
@@ -2116,6 +2173,8 @@
|
|||||||
|
|
||||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||||
|
|
||||||
|
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||||
|
|
||||||
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
|
"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=="],
|
"astro": ["astro@5.7.13", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="],
|
||||||
@@ -2144,6 +2203,8 @@
|
|||||||
|
|
||||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
"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=="],
|
"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=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
@@ -2258,7 +2319,7 @@
|
|||||||
|
|
||||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
"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=="],
|
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
||||||
|
|
||||||
@@ -2274,6 +2335,8 @@
|
|||||||
|
|
||||||
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
"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": ["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=="],
|
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||||
@@ -2368,6 +2431,8 @@
|
|||||||
|
|
||||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
"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=="],
|
"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=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
@@ -2388,6 +2453,8 @@
|
|||||||
|
|
||||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||||
|
|
||||||
|
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
"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": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||||
@@ -2440,6 +2507,8 @@
|
|||||||
|
|
||||||
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
|
"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=="],
|
"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=="],
|
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||||
@@ -2834,6 +2903,8 @@
|
|||||||
|
|
||||||
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
|
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
|
||||||
|
|
||||||
|
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||||
@@ -3074,6 +3145,8 @@
|
|||||||
|
|
||||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
"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=="],
|
"lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||||
@@ -3084,6 +3157,8 @@
|
|||||||
|
|
||||||
"luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
|
"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=="],
|
"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=="],
|
"magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
|
||||||
@@ -3234,6 +3309,8 @@
|
|||||||
|
|
||||||
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||||
|
|
||||||
|
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
|
||||||
|
|
||||||
"miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="],
|
"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=="],
|
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||||
@@ -3426,6 +3503,8 @@
|
|||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||||
|
|
||||||
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
|
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
|
||||||
|
|
||||||
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
||||||
@@ -3492,6 +3571,8 @@
|
|||||||
|
|
||||||
"pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="],
|
"pretty": ["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=="],
|
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||||
|
|
||||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||||
@@ -3534,8 +3615,12 @@
|
|||||||
|
|
||||||
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
|
"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-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-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=="],
|
"react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="],
|
||||||
@@ -3560,6 +3645,8 @@
|
|||||||
|
|
||||||
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
"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-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=="],
|
"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 +3655,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=="],
|
"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=="],
|
"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=="],
|
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
||||||
@@ -3760,7 +3849,7 @@
|
|||||||
|
|
||||||
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
"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=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
@@ -3808,6 +3897,10 @@
|
|||||||
|
|
||||||
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
|
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
|
||||||
|
|
||||||
|
"storybook": ["storybook@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg=="],
|
||||||
|
|
||||||
|
"storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="],
|
||||||
|
|
||||||
"stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="],
|
"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=="],
|
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
|
||||||
@@ -3834,6 +3927,8 @@
|
|||||||
|
|
||||||
"strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
|
"strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
|
||||||
|
|
||||||
|
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
||||||
|
|
||||||
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
|
"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=="],
|
"strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
|
||||||
@@ -3896,6 +3991,8 @@
|
|||||||
|
|
||||||
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
|
"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=="],
|
"titleize": ["titleize@4.0.0", "", {}, "sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g=="],
|
||||||
|
|
||||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||||
@@ -3920,6 +4017,8 @@
|
|||||||
|
|
||||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
"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=="],
|
"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=="],
|
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||||
@@ -4020,6 +4119,8 @@
|
|||||||
|
|
||||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
"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=="],
|
"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=="],
|
"unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="],
|
||||||
@@ -4032,6 +4133,8 @@
|
|||||||
|
|
||||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||||
|
|
||||||
|
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||||
|
|
||||||
"utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
|
"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=="],
|
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
|
||||||
@@ -4106,6 +4209,8 @@
|
|||||||
|
|
||||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
"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-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=="],
|
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||||
@@ -4260,6 +4365,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/@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/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=="],
|
"@astrojs/solid-js/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
|
||||||
@@ -4488,6 +4595,8 @@
|
|||||||
|
|
||||||
"@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
|
"@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
|
||||||
|
|
||||||
|
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
"@modelcontextprotocol/sdk/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=="],
|
"@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
||||||
@@ -4632,8 +4741,18 @@
|
|||||||
|
|
||||||
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||||
|
|
||||||
|
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||||
|
|
||||||
|
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||||
|
|
||||||
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
"@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=="],
|
"@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=="],
|
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
@@ -4692,8 +4811,6 @@
|
|||||||
|
|
||||||
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
"clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
|
||||||
|
|
||||||
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
"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=="],
|
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
|
||||||
@@ -4714,6 +4831,8 @@
|
|||||||
|
|
||||||
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||||
|
|
||||||
|
"estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
||||||
|
|
||||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||||
@@ -4814,6 +4933,10 @@
|
|||||||
|
|
||||||
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
|
||||||
|
|
||||||
|
"pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||||
|
|
||||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||||
|
|
||||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||||
@@ -4842,12 +4965,16 @@
|
|||||||
|
|
||||||
"sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
|
"sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
|
||||||
|
|
||||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
|
||||||
|
|
||||||
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
|
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
|
||||||
|
|
||||||
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
"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/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=="],
|
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
@@ -4880,6 +5007,10 @@
|
|||||||
|
|
||||||
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
"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/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=="],
|
"vitest/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
|
||||||
@@ -5210,6 +5341,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=="],
|
"@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=="],
|
"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=="],
|
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
|
||||||
@@ -5304,6 +5437,60 @@
|
|||||||
|
|
||||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||||
|
|
||||||
|
"storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||||
|
|
||||||
|
"storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||||
|
|
||||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||||
@@ -5372,6 +5559,8 @@
|
|||||||
|
|
||||||
"vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
|
"vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
|
||||||
|
|
||||||
|
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||||
|
|
||||||
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
"wrangler/esbuild/@esbuild/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=="],
|
"wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
|
|||||||
})
|
})
|
||||||
|
|
||||||
const zenLiteProduct = new stripe.Product("ZenLite", {
|
const zenLiteProduct = new stripe.Product("ZenLite", {
|
||||||
name: "OpenCode Lite",
|
name: "OpenCode Go",
|
||||||
})
|
})
|
||||||
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
||||||
product: zenLiteProduct.id,
|
product: zenLiteProduct.id,
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-3hfy6nfEnGq4J6inH0pXANw05oas+81iuayn7J0pj9c=",
|
"x86_64-linux": "sha256-2XLuizbG90QDUQL+1M90XxfVZxjkIQ1cFYS46nnVO7g=",
|
||||||
"aarch64-linux": "sha256-dxWaLtzSeI5NfHwB6u0K10yxoA0ESz/r+zTEQ3FdKFY=",
|
"aarch64-linux": "sha256-hlckiGAtbpAlwgcE7KgzKKRq9T2FEOSq3Q1MhuHfZ2c=",
|
||||||
"aarch64-darwin": "sha256-kkK4rj4g0j2jJFXVmVH7CJcXlI8Dj/KmL/VC3iE4Z+8=",
|
"aarch64-darwin": "sha256-V/8Kay+5bDb/BSVgBQhSMwzmRmkNGl3U0HFMVbVcMak=",
|
||||||
"x86_64-darwin": "sha256-jt51irxZd48kb0BItd8InP7lfsELUh0unVYO2es+a98="
|
"x86_64-darwin": "sha256-duLDF88Q/hXK5jwBy4dVxMSiTTS0R4obp9MlTuOF/Pw="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.9",
|
"packageManager": "bun@1.3.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"dev:desktop": "bun --cwd packages/desktop tauri dev",
|
"dev:desktop": "bun --cwd packages/desktop tauri dev",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"@tsconfig/bun": "1.0.9",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@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",
|
"@solid-primitives/storage": "4.3.3",
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ test("file tree can expand folders and open a file", async ({ page, gotoSession
|
|||||||
await tab.click()
|
await tab.click()
|
||||||
await expect(tab).toHaveAttribute("aria-selected", "true")
|
await expect(tab).toHaveAttribute("aria-selected", "true")
|
||||||
|
|
||||||
const code = page.locator('[data-component="code"]').first()
|
const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
|
||||||
await expect(code).toBeVisible()
|
await expect(viewer).toBeVisible()
|
||||||
await expect(code).toContainText("export default function FileTree")
|
await expect(viewer).toContainText("export default function FileTree")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { promptSelector } from "../selectors"
|
import { promptSelector } from "../selectors"
|
||||||
|
import { modKey } from "../utils"
|
||||||
|
|
||||||
test("smoke file viewer renders real file content", async ({ page, gotoSession }) => {
|
test("smoke file viewer renders real file content", async ({ page, gotoSession }) => {
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
@@ -43,7 +44,60 @@ test("smoke file viewer renders real file content", async ({ page, gotoSession }
|
|||||||
await expect(tab).toBeVisible()
|
await expect(tab).toBeVisible()
|
||||||
await tab.click()
|
await tab.click()
|
||||||
|
|
||||||
const code = page.locator('[data-component="code"]').first()
|
const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
|
||||||
await expect(code).toBeVisible()
|
await expect(viewer).toBeVisible()
|
||||||
await expect(code.getByText(/"name"\s*:\s*"@opencode-ai\/app"/)).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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
sessionIDFromUrl,
|
sessionIDFromUrl,
|
||||||
} from "../actions"
|
} from "../actions"
|
||||||
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
|
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
|
||||||
import { createSdk, dirSlug } from "../utils"
|
import { createSdk, dirSlug, sessionPath } from "../utils"
|
||||||
|
|
||||||
function slugFromUrl(url: string) {
|
function slugFromUrl(url: string) {
|
||||||
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
|
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
|
||||||
@@ -51,7 +51,6 @@ test("switching back to a project opens the latest workspace session", async ({
|
|||||||
|
|
||||||
const other = await createTestProject()
|
const other = await createTestProject()
|
||||||
const otherSlug = dirSlug(other)
|
const otherSlug = dirSlug(other)
|
||||||
const stamp = Date.now()
|
|
||||||
let rootDir: string | undefined
|
let rootDir: string | undefined
|
||||||
let workspaceDir: string | undefined
|
let workspaceDir: string | undefined
|
||||||
let sessionID: string | undefined
|
let sessionID: string | undefined
|
||||||
@@ -80,6 +79,7 @@ test("switching back to a project opens the latest workspace session", async ({
|
|||||||
|
|
||||||
const workspaceSlug = slugFromUrl(page.url())
|
const workspaceSlug = slugFromUrl(page.url())
|
||||||
workspaceDir = base64Decode(workspaceSlug)
|
workspaceDir = base64Decode(workspaceSlug)
|
||||||
|
if (!workspaceDir) throw new Error(`Failed to decode workspace slug: ${workspaceSlug}`)
|
||||||
await openSidebar(page)
|
await openSidebar(page)
|
||||||
|
|
||||||
const workspace = page.locator(workspaceItemSelector(workspaceSlug)).first()
|
const workspace = page.locator(workspaceItemSelector(workspaceSlug)).first()
|
||||||
@@ -92,15 +92,14 @@ test("switching back to a project opens the latest workspace session", async ({
|
|||||||
|
|
||||||
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`))
|
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`))
|
||||||
|
|
||||||
const prompt = page.locator(promptSelector)
|
const created = await createSdk(workspaceDir)
|
||||||
await expect(prompt).toBeVisible()
|
.session.create()
|
||||||
await prompt.fill(`project switch remembers workspace ${stamp}`)
|
.then((x) => x.data?.id)
|
||||||
await prompt.press("Enter")
|
if (!created) throw new Error(`Failed to create session for workspace: ${workspaceDir}`)
|
||||||
|
|
||||||
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
|
sessionID = created
|
||||||
|
|
||||||
|
await page.goto(sessionPath(workspaceDir, created))
|
||||||
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
|
await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
|
||||||
|
|
||||||
await openSidebar(page)
|
await openSidebar(page)
|
||||||
@@ -114,7 +113,8 @@ test("switching back to a project opens the latest workspace session", async ({
|
|||||||
await expect(rootButton).toBeVisible()
|
await expect(rootButton).toBeVisible()
|
||||||
await rootButton.click()
|
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] },
|
{ extra: [other] },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { clearSessionDockSeed, seedSessionPermission, seedSessionQuestion, seedSessionTodos } from "../actions"
|
import { clearSessionDockSeed, seedSessionQuestion, seedSessionTodos } from "../actions"
|
||||||
import {
|
import {
|
||||||
permissionDockSelector,
|
permissionDockSelector,
|
||||||
promptSelector,
|
promptSelector,
|
||||||
@@ -11,11 +11,23 @@ import {
|
|||||||
} from "../selectors"
|
} from "../selectors"
|
||||||
|
|
||||||
type Sdk = Parameters<typeof clearSessionDockSeed>[0]
|
type Sdk = Parameters<typeof clearSessionDockSeed>[0]
|
||||||
|
type PermissionRule = { permission: string; pattern: string; action: "allow" | "deny" | "ask" }
|
||||||
|
|
||||||
async function withDockSession<T>(sdk: Sdk, title: string, fn: (session: { id: string; title: string }) => Promise<T>) {
|
async function withDockSession<T>(
|
||||||
const session = await sdk.session.create({ title }).then((r) => r.data)
|
sdk: Sdk,
|
||||||
|
title: string,
|
||||||
|
fn: (session: { id: string; title: string }) => Promise<T>,
|
||||||
|
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")
|
if (!session?.id) throw new Error("Session create did not return an id")
|
||||||
return fn(session)
|
try {
|
||||||
|
return await fn(session)
|
||||||
|
} finally {
|
||||||
|
await sdk.session.delete({ sessionID: session.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test.setTimeout(120_000)
|
test.setTimeout(120_000)
|
||||||
@@ -28,6 +40,94 @@ async function withDockSeed<T>(sdk: Sdk, sessionID: string, fn: () => Promise<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<T>(
|
||||||
|
page: any,
|
||||||
|
request: {
|
||||||
|
id: string
|
||||||
|
sessionID: string
|
||||||
|
permission: string
|
||||||
|
patterns: string[]
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
always?: string[]
|
||||||
|
},
|
||||||
|
opts: { child?: any } | undefined,
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
) {
|
||||||
|
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 }) => {
|
test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock default", async (session) => {
|
await withDockSession(sdk, "e2e composer dock default", async (session) => {
|
||||||
await gotoSession(session.id)
|
await gotoSession(session.id)
|
||||||
@@ -76,72 +176,179 @@ test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSess
|
|||||||
|
|
||||||
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
|
||||||
await gotoSession(session.id)
|
await gotoSession(session.id)
|
||||||
|
await setAutoAccept(page, false)
|
||||||
await seedSessionPermission(sdk, {
|
await withMockPermission(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
id: "per_e2e_once",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: ["README.md"],
|
patterns: ["/tmp/opencode-e2e-perm-once"],
|
||||||
description: "Need permission for command",
|
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.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
await page
|
await clearPermissionDock(page, /allow once/i)
|
||||||
.locator(permissionDockSelector)
|
await page.goto(page.url())
|
||||||
.getByRole("button", { name: /allow once/i })
|
|
||||||
.click()
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
})
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
|
||||||
await gotoSession(session.id)
|
await gotoSession(session.id)
|
||||||
|
await setAutoAccept(page, false)
|
||||||
await seedSessionPermission(sdk, {
|
await withMockPermission(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
id: "per_e2e_reject",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
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.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
await page.locator(permissionDockSelector).getByRole("button", { name: /deny/i }).click()
|
await clearPermissionDock(page, /deny/i)
|
||||||
|
await page.goto(page.url())
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
})
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
|
||||||
await gotoSession(session.id)
|
await gotoSession(session.id)
|
||||||
|
await setAutoAccept(page, false)
|
||||||
await seedSessionPermission(sdk, {
|
await withMockPermission(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
id: "per_e2e_always",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: ["README.md"],
|
patterns: ["/tmp/opencode-e2e-perm-always"],
|
||||||
description: "Need permission for command",
|
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.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
await page
|
await clearPermissionDock(page, /allow always/i)
|
||||||
.locator(permissionDockSelector)
|
await page.goto(page.url())
|
||||||
.getByRole("button", { name: /allow always/i })
|
|
||||||
.click()
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
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")
|
||||||
|
|
||||||
|
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" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
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 sdk.session.delete({ sessionID: child.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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 sdk.session.delete({ sessionID: child.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } 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 serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
||||||
|
|
||||||
export const serverUrl = `http://${serverHost}:${serverPort}`
|
export const serverUrl = `http://${serverHost}:${serverPort}`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.15",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test"
|
import { defineConfig, devices } from "@playwright/test"
|
||||||
|
|
||||||
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
|
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
|
||||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${port}`
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`
|
||||||
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
|
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
||||||
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
||||||
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
|
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
|
||||||
const reuse = !process.env.CI
|
const reuse = !process.env.CI
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ try {
|
|||||||
Object.assign(process.env, serverEnv)
|
Object.assign(process.env, serverEnv)
|
||||||
process.env.AGENT = "1"
|
process.env.AGENT = "1"
|
||||||
process.env.OPENCODE = "1"
|
process.env.OPENCODE = "1"
|
||||||
|
process.env.OPENCODE_PID = String(process.pid)
|
||||||
|
|
||||||
const log = await import("../../opencode/src/util/log")
|
const log = await import("../../opencode/src/util/log")
|
||||||
const install = await import("../../opencode/src/installation")
|
const install = await import("../../opencode/src/installation")
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import "@/index.css"
|
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 { I18nProvider } from "@opencode-ai/ui/context"
|
||||||
import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
|
|
||||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
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 { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||||
import { Diff } from "@opencode-ai/ui/diff"
|
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme"
|
import { ThemeProvider } from "@opencode-ai/ui/theme"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
@@ -122,9 +120,7 @@ export function AppBaseProviders(props: ParentProps) {
|
|||||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||||
<DialogProvider>
|
<DialogProvider>
|
||||||
<MarkedProviderWithNativeParser>
|
<MarkedProviderWithNativeParser>
|
||||||
<DiffComponentProvider component={Diff}>
|
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||||
<CodeComponentProvider component={Code}>{props.children}</CodeComponentProvider>
|
|
||||||
</DiffComponentProvider>
|
|
||||||
</MarkedProviderWithNativeParser>
|
</MarkedProviderWithNativeParser>
|
||||||
</DialogProvider>
|
</DialogProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
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 { List, type ListRef } from "@opencode-ai/ui/list"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
@@ -447,7 +446,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-6 px-2.5 pb-3">
|
<div class="flex flex-col gap-6 px-2.5 pb-3">
|
||||||
<div class="px-2.5 flex gap-4 items-center">
|
<div class="px-2.5 flex gap-4 items-center">
|
||||||
<ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={props.provider} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<div class="text-16-medium text-text-strong">
|
<div class="text-16-medium text-text-strong">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
|
<Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createSignal } from "solid-js"
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
|
|
||||||
export type Highlight = {
|
export type Highlight = {
|
||||||
@@ -16,6 +17,7 @@ export type Highlight = {
|
|||||||
|
|
||||||
export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
|
export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
const language = useLanguage()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const [index, setIndex] = createSignal(0)
|
const [index, setIndex] = createSignal(0)
|
||||||
|
|
||||||
@@ -83,16 +85,16 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
|
|||||||
<div class="flex flex-col items-start gap-3">
|
<div class="flex flex-col items-start gap-3">
|
||||||
{isLast() ? (
|
{isLast() ? (
|
||||||
<Button variant="primary" size="large" onClick={handleClose}>
|
<Button variant="primary" size="large" onClick={handleClose}>
|
||||||
Get started
|
{language.t("dialog.releaseNotes.action.getStarted")}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="secondary" size="large" onClick={handleNext}>
|
<Button variant="secondary" size="large" onClick={handleNext}>
|
||||||
Next
|
{language.t("dialog.releaseNotes.action.next")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button variant="ghost" size="small" onClick={handleDisable}>
|
<Button variant="ghost" size="small" onClick={handleDisable}>
|
||||||
Don't show these in the future
|
{language.t("dialog.releaseNotes.action.hideFuture")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
|
|||||||
{feature()!.media!.type === "image" ? (
|
{feature()!.media!.type === "image" ? (
|
||||||
<img
|
<img
|
||||||
src={feature()!.media!.src}
|
src={feature()!.media!.src}
|
||||||
alt={feature()!.media!.alt ?? feature()?.title ?? "Release preview"}
|
alt={feature()!.media!.alt ?? feature()?.title ?? language.t("dialog.releaseNotes.media.alt")}
|
||||||
class="w-full h-full object-cover"
|
class="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
|
|||||||
import { createMemo, createResource, createSignal } from "solid-js"
|
import { createMemo, createResource, createSignal } from "solid-js"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
interface DialogSelectDirectoryProps {
|
interface DialogSelectDirectoryProps {
|
||||||
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
|
|||||||
type Row = {
|
type Row = {
|
||||||
absolute: string
|
absolute: string
|
||||||
search: string
|
search: string
|
||||||
|
group: "recent" | "folders"
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanInput(value: string) {
|
function cleanInput(value: string) {
|
||||||
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
|
|||||||
return tildeOf(full, home) || full
|
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 full = trimTrailing(absolute)
|
||||||
const tilde = tildeOf(full, home)
|
const tilde = tildeOf(full, home)
|
||||||
const withSlash = (value: string) => {
|
const withSlash = (value: string) => {
|
||||||
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
|
|||||||
const search = Array.from(
|
const search = Array.from(
|
||||||
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
|
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
|
||||||
).join("\n")
|
).join("\n")
|
||||||
return { absolute: full, search }
|
return { absolute: full, search, group }
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueRows(rows: Row[]) {
|
||||||
|
const seen = new Set<string>()
|
||||||
|
return rows.filter((row) => {
|
||||||
|
if (seen.has(row.absolute)) return false
|
||||||
|
seen.add(row.absolute)
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function useDirectorySearch(args: {
|
function useDirectorySearch(args: {
|
||||||
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
|
|||||||
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
const sdk = useGlobalSDK()
|
const sdk = useGlobalSDK()
|
||||||
|
const layout = useLayout()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
|||||||
start,
|
start,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const recentProjects = createMemo(() => {
|
||||||
|
const projects = layout.projects.list()
|
||||||
|
const byProject = new Map<string, number>()
|
||||||
|
|
||||||
|
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 items = async (value: string) => {
|
||||||
const results = await directories(value)
|
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) {
|
function resolve(absolute: string) {
|
||||||
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
|||||||
items={items}
|
items={items}
|
||||||
key={(x) => x.absolute}
|
key={(x) => x.absolute}
|
||||||
filterKeys={["search"]}
|
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)}
|
ref={(r) => (list = r)}
|
||||||
onFilter={(value) => setFilter(cleanInput(value))}
|
onFilter={(value) => setFilter(cleanInput(value))}
|
||||||
onKeyEvent={(e, item) => {
|
onKeyEvent={(e, item) => {
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
</div>
|
</div>
|
||||||
<Show when={item.updated}>
|
<Show when={item.updated}>
|
||||||
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
|
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
|
||||||
{getRelativeTime(new Date(item.updated!).toISOString())}
|
{getRelativeTime(new Date(item.updated!).toISOString(), language.t)}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/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 { List, type ListRef } from "@opencode-ai/ui/list"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
@@ -95,11 +94,22 @@ export const DialogSelectModelUnpaid: Component = () => {
|
|||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div class="w-full flex items-center gap-x-3">
|
<div class="w-full flex items-center gap-x-3">
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={i.id as IconName} />
|
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
||||||
<span>{i.name}</span>
|
<span>{i.name}</span>
|
||||||
|
<Show when={i.id === "opencode"}>
|
||||||
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
|
||||||
|
</Show>
|
||||||
<Show when={i.id === "opencode"}>
|
<Show when={i.id === "opencode"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={i.id === "opencode-go"}>
|
||||||
|
<>
|
||||||
|
<div class="text-14-regular text-text-weak">
|
||||||
|
{language.t("dialog.provider.opencodeGo.tagline")}
|
||||||
|
</div>
|
||||||
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
|
</>
|
||||||
|
</Show>
|
||||||
<Show when={i.id === "anthropic"}>
|
<Show when={i.id === "anthropic"}>
|
||||||
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -5,18 +5,12 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
|||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
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 { DialogConnectProvider } from "./dialog-connect-provider"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { DialogCustomProvider } from "./dialog-custom-provider"
|
import { DialogCustomProvider } from "./dialog-custom-provider"
|
||||||
|
|
||||||
const CUSTOM_ID = "_custom"
|
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 = () => {
|
export const DialogSelectProvider: Component = () => {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
@@ -29,6 +23,7 @@ export const DialogSelectProvider: Component = () => {
|
|||||||
if (id === "anthropic") return language.t("dialog.provider.anthropic.note")
|
if (id === "anthropic") return language.t("dialog.provider.anthropic.note")
|
||||||
if (id === "openai") return language.t("dialog.provider.openai.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.startsWith("github-copilot")) return language.t("dialog.provider.copilot.note")
|
||||||
|
if (id === "opencode-go") return language.t("dialog.provider.opencodeGo.tagline")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -68,8 +63,11 @@ export const DialogSelectProvider: Component = () => {
|
|||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
<div class="px-1.25 w-full flex items-center gap-x-3">
|
<div class="px-1.25 w-full flex items-center gap-x-3">
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={icon(i.id)} />
|
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
||||||
<span>{i.name}</span>
|
<span>{i.name}</span>
|
||||||
|
<Show when={i.id === "opencode"}>
|
||||||
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
|
||||||
|
</Show>
|
||||||
<Show when={i.id === CUSTOM_ID}>
|
<Show when={i.id === CUSTOM_ID}>
|
||||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -77,6 +75,9 @@ export const DialogSelectProvider: Component = () => {
|
|||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
|
<Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
|
||||||
|
<Show when={i.id === "opencode-go"}>
|
||||||
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button } from "@opencode-ai/ui/button"
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
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 { useNavigate } from "@solidjs/router"
|
||||||
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
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 { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||||
import { checkServerHealth, type ServerHealth } from "@/utils/server-health"
|
import { checkServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||||
|
|
||||||
interface AddRowProps {
|
interface ServerFormProps {
|
||||||
value: string
|
|
||||||
placeholder: string
|
|
||||||
adding: boolean
|
|
||||||
error: string
|
|
||||||
status: boolean | undefined
|
|
||||||
onChange: (value: string) => void
|
|
||||||
onKeyDown: (event: KeyboardEvent) => void
|
|
||||||
onBlur: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditRowProps {
|
|
||||||
value: string
|
value: string
|
||||||
|
name: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
busy: boolean
|
busy: boolean
|
||||||
error: string
|
error: string
|
||||||
status: boolean | undefined
|
status: boolean | undefined
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
onKeyDown: (event: KeyboardEvent) => void
|
onNameChange: (value: string) => void
|
||||||
onBlur: () => void
|
onUsernameChange: (value: string) => void
|
||||||
|
onPasswordChange: (value: string) => void
|
||||||
|
onSubmit: () => void
|
||||||
|
onBack: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRequestError(language: ReturnType<typeof useLanguage>, err: unknown) {
|
function showRequestError(language: ReturnType<typeof useLanguage>, err: unknown) {
|
||||||
@@ -83,73 +79,47 @@ function useServerPreview(fetcher: typeof fetch) {
|
|||||||
return host.includes(".") || host.includes(":")
|
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)
|
setStatus(undefined)
|
||||||
if (!looksComplete(value)) return
|
if (!looksComplete(value)) return
|
||||||
const normalized = normalizeServerUrl(value)
|
const normalized = normalizeServerUrl(value)
|
||||||
if (!normalized) return
|
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)
|
setStatus(result.healthy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { previewStatus }
|
return { previewStatus }
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddRow(props: AddRowProps) {
|
function ServerForm(props: ServerFormProps) {
|
||||||
return (
|
const language = useLanguage()
|
||||||
<div class="flex items-center px-4 min-h-14 py-3 min-w-0 flex-1">
|
const keyDown = (event: KeyboardEvent) => {
|
||||||
<div class="flex-1 min-w-0 [&_[data-slot=input-wrapper]]:relative">
|
event.stopPropagation()
|
||||||
<div
|
if (event.key === "Escape") {
|
||||||
classList={{
|
event.preventDefault()
|
||||||
"size-1.5 rounded-full absolute left-3 top-1/2 -translate-y-1/2 z-10 pointer-events-none": true,
|
props.onBack()
|
||||||
"bg-icon-success-base": props.status === true,
|
return
|
||||||
"bg-icon-critical-base": props.status === false,
|
}
|
||||||
"bg-border-weak-base": props.status === undefined,
|
if (event.key !== "Enter" || event.isComposing) return
|
||||||
}}
|
event.preventDefault()
|
||||||
ref={(el) => {
|
props.onSubmit()
|
||||||
// Position relative to input-wrapper
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const wrapper = el.parentElement?.querySelector('[data-slot="input-wrapper"]')
|
|
||||||
if (wrapper instanceof HTMLElement) {
|
|
||||||
wrapper.appendChild(el)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
type="text"
|
|
||||||
hideLabel
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
value={props.value}
|
|
||||||
autofocus
|
|
||||||
validationState={props.error ? "invalid" : "valid"}
|
|
||||||
error={props.error}
|
|
||||||
disabled={props.adding}
|
|
||||||
onChange={props.onChange}
|
|
||||||
onKeyDown={props.onKeyDown}
|
|
||||||
onBlur={props.onBlur}
|
|
||||||
class="pl-7"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function EditRow(props: EditRowProps) {
|
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-3 px-4 min-w-0 flex-1" onClick={(event) => event.stopPropagation()}>
|
<div class="px-5">
|
||||||
<div
|
<div class="bg-surface-raised-base rounded-md p-5 flex flex-col gap-3">
|
||||||
classList={{
|
<div class="flex-1 min-w-0 [&_[data-slot=input-wrapper]]:relative">
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
|
||||||
"bg-icon-success-base": props.status === true,
|
|
||||||
"bg-icon-critical-base": props.status === false,
|
|
||||||
"bg-border-weak-base": props.status === undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<TextField
|
<TextField
|
||||||
type="text"
|
type="text"
|
||||||
hideLabel
|
label={language.t("dialog.server.add.url")}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
autofocus
|
autofocus
|
||||||
@@ -157,10 +127,39 @@ function EditRow(props: EditRowProps) {
|
|||||||
error={props.error}
|
error={props.error}
|
||||||
disabled={props.busy}
|
disabled={props.busy}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
onKeyDown={props.onKeyDown}
|
onKeyDown={keyDown}
|
||||||
onBlur={props.onBlur}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label={language.t("dialog.server.add.name")}
|
||||||
|
placeholder={language.t("dialog.server.add.namePlaceholder")}
|
||||||
|
value={props.name}
|
||||||
|
disabled={props.busy}
|
||||||
|
onChange={props.onNameChange}
|
||||||
|
onKeyDown={keyDown}
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-2 gap-2 min-w-0">
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label={language.t("dialog.server.add.username")}
|
||||||
|
placeholder="username"
|
||||||
|
value={props.username}
|
||||||
|
disabled={props.busy}
|
||||||
|
onChange={props.onUsernameChange}
|
||||||
|
onKeyDown={keyDown}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type="password"
|
||||||
|
label={language.t("dialog.server.add.password")}
|
||||||
|
placeholder="password"
|
||||||
|
value={props.password}
|
||||||
|
disabled={props.busy}
|
||||||
|
onChange={props.onPasswordChange}
|
||||||
|
onKeyDown={keyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -174,11 +173,13 @@ export function DialogSelectServer() {
|
|||||||
const fetcher = platform.fetch ?? globalThis.fetch
|
const fetcher = platform.fetch ?? globalThis.fetch
|
||||||
const { defaultUrl, canDefault, setDefault } = useDefaultServer(platform, language)
|
const { defaultUrl, canDefault, setDefault } = useDefaultServer(platform, language)
|
||||||
const { previewStatus } = useServerPreview(fetcher)
|
const { previewStatus } = useServerPreview(fetcher)
|
||||||
let listRoot: HTMLDivElement | undefined
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
status: {} as Record<ServerConnection.Key, ServerHealth | undefined>,
|
status: {} as Record<ServerConnection.Key, ServerHealth | undefined>,
|
||||||
addServer: {
|
addServer: {
|
||||||
url: "",
|
url: "",
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
adding: false,
|
adding: false,
|
||||||
error: "",
|
error: "",
|
||||||
showForm: false,
|
showForm: false,
|
||||||
@@ -187,6 +188,9 @@ export function DialogSelectServer() {
|
|||||||
editServer: {
|
editServer: {
|
||||||
id: undefined as string | undefined,
|
id: undefined as string | undefined,
|
||||||
value: "",
|
value: "",
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
error: "",
|
error: "",
|
||||||
busy: false,
|
busy: false,
|
||||||
status: undefined as boolean | undefined,
|
status: undefined as boolean | undefined,
|
||||||
@@ -196,27 +200,32 @@ export function DialogSelectServer() {
|
|||||||
const resetAdd = () => {
|
const resetAdd = () => {
|
||||||
setStore("addServer", {
|
setStore("addServer", {
|
||||||
url: "",
|
url: "",
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
adding: false,
|
||||||
error: "",
|
error: "",
|
||||||
showForm: false,
|
showForm: false,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetEdit = () => {
|
const resetEdit = () => {
|
||||||
setStore("editServer", {
|
setStore("editServer", {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
value: "",
|
value: "",
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
error: "",
|
error: "",
|
||||||
status: undefined,
|
status: undefined,
|
||||||
busy: false,
|
busy: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceServer = (original: ServerConnection.Http, next: string) => {
|
const replaceServer = (original: ServerConnection.Http, next: ServerConnection.Http) => {
|
||||||
const active = server.key
|
const active = server.key
|
||||||
const newConn = server.add(next)
|
const newConn = server.add(next)
|
||||||
if (!newConn) return
|
if (!newConn) return
|
||||||
|
|
||||||
const nextActive = active === ServerConnection.key(original) ? ServerConnection.key(newConn) : active
|
const nextActive = active === ServerConnection.key(original) ? ServerConnection.key(newConn) : active
|
||||||
if (nextActive) server.setActive(nextActive)
|
if (nextActive) server.setActive(nextActive)
|
||||||
server.remove(ServerConnection.key(original))
|
server.remove(ServerConnection.key(original))
|
||||||
@@ -271,8 +280,8 @@ export function DialogSelectServer() {
|
|||||||
async function select(conn: ServerConnection.Any, persist?: boolean) {
|
async function select(conn: ServerConnection.Any, persist?: boolean) {
|
||||||
if (!persist && store.status[ServerConnection.key(conn)]?.healthy === false) return
|
if (!persist && store.status[ServerConnection.key(conn)]?.healthy === false) return
|
||||||
dialog.close()
|
dialog.close()
|
||||||
if (persist) {
|
if (persist && conn.type === "http") {
|
||||||
server.add(conn.http.url)
|
server.add(conn)
|
||||||
navigate("/")
|
navigate("/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -283,21 +292,59 @@ export function DialogSelectServer() {
|
|||||||
const handleAddChange = (value: string) => {
|
const handleAddChange = (value: string) => {
|
||||||
if (store.addServer.adding) return
|
if (store.addServer.adding) return
|
||||||
setStore("addServer", { url: value, error: "" })
|
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 handleAddNameChange = (value: string) => {
|
||||||
const scroll = listRoot?.querySelector<HTMLDivElement>('[data-slot="list-scroll"]')
|
if (store.addServer.adding) return
|
||||||
if (!scroll) return
|
setStore("addServer", { name: value, error: "" })
|
||||||
requestAnimationFrame(() => {
|
}
|
||||||
scroll.scrollTop = scroll.scrollHeight
|
|
||||||
})
|
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) => {
|
const handleEditChange = (value: string) => {
|
||||||
if (store.editServer.busy) return
|
if (store.editServer.busy) return
|
||||||
setStore("editServer", { value, error: "" })
|
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) {
|
async function handleAdd(value: string) {
|
||||||
@@ -310,16 +357,22 @@ export function DialogSelectServer() {
|
|||||||
|
|
||||||
setStore("addServer", { adding: true, error: "" })
|
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 })
|
setStore("addServer", { adding: false })
|
||||||
|
|
||||||
if (!result.healthy) {
|
if (!result.healthy) {
|
||||||
setStore("addServer", { error: language.t("dialog.server.add.error") })
|
setStore("addServer", { error: language.t("dialog.server.add.error") })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resetAdd()
|
resetAdd()
|
||||||
await select({ type: "http", http: { url: normalized } }, true)
|
await select(conn, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEdit(original: ServerConnection.Any, value: string) {
|
async function handleEdit(original: ServerConnection.Any, value: string) {
|
||||||
@@ -330,53 +383,115 @@ export function DialogSelectServer() {
|
|||||||
return
|
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()
|
resetEdit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStore("editServer", { busy: true, error: "" })
|
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 })
|
setStore("editServer", { busy: false })
|
||||||
|
|
||||||
if (!result.healthy) {
|
if (!result.healthy) {
|
||||||
setStore("editServer", { error: language.t("dialog.server.add.error") })
|
setStore("editServer", { error: language.t("dialog.server.add.error") })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (normalized === original.http.url) {
|
||||||
replaceServer(original, normalized)
|
server.add(conn)
|
||||||
|
} else {
|
||||||
|
replaceServer(original, conn)
|
||||||
|
}
|
||||||
|
|
||||||
resetEdit()
|
resetEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddKey = (event: KeyboardEvent) => {
|
const mode = createMemo<"list" | "add" | "edit">(() => {
|
||||||
event.stopPropagation()
|
if (store.editServer.id) return "edit"
|
||||||
if (event.key !== "Enter" || event.isComposing) return
|
if (store.addServer.showForm) return "add"
|
||||||
event.preventDefault()
|
return "list"
|
||||||
handleAdd(store.addServer.url)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const blurAdd = () => {
|
const editing = createMemo(() => {
|
||||||
if (!store.addServer.url.trim()) {
|
if (!store.editServer.id) return
|
||||||
|
return items().find((x) => x.type === "http" && x.http.url === store.editServer.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
resetAdd()
|
resetAdd()
|
||||||
return
|
resetEdit()
|
||||||
}
|
|
||||||
handleAdd(store.addServer.url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditKey = (event: KeyboardEvent, original: ServerConnection.Any) => {
|
const startAdd = () => {
|
||||||
event.stopPropagation()
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
event.preventDefault()
|
|
||||||
resetEdit()
|
resetEdit()
|
||||||
|
setStore("addServer", {
|
||||||
|
showForm: true,
|
||||||
|
url: "",
|
||||||
|
name: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
error: "",
|
||||||
|
status: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
if (event.key !== "Enter" || event.isComposing) return
|
const original = editing()
|
||||||
event.preventDefault()
|
if (!original) return
|
||||||
handleEdit(original, store.editServer.value)
|
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 (
|
||||||
|
<div class="flex items-center gap-2 -ml-2">
|
||||||
|
<IconButton icon="arrow-left" variant="ghost" onClick={resetForm} aria-label={language.t("common.goBack")} />
|
||||||
|
<span>{isAddMode() ? language.t("dialog.server.add.title") : language.t("dialog.server.edit.title")}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!store.editServer.id) return
|
||||||
|
if (editing()) return
|
||||||
|
resetEdit()
|
||||||
|
})
|
||||||
|
|
||||||
async function handleRemove(url: ServerConnection.Key) {
|
async function handleRemove(url: ServerConnection.Key) {
|
||||||
server.remove(url)
|
server.remove(url)
|
||||||
if ((await platform.getDefaultServerUrl?.()) === url) {
|
if ((await platform.getDefaultServerUrl?.()) === url) {
|
||||||
@@ -385,9 +500,29 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={language.t("dialog.server.title")}>
|
<Dialog title={formTitle()}>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div ref={(el) => (listRoot = el)}>
|
<Show
|
||||||
|
when={!isFormMode()}
|
||||||
|
fallback={
|
||||||
|
<ServerForm
|
||||||
|
value={isAddMode() ? store.addServer.url : store.editServer.value}
|
||||||
|
name={isAddMode() ? store.addServer.name : store.editServer.name}
|
||||||
|
username={isAddMode() ? store.addServer.username : store.editServer.username}
|
||||||
|
password={isAddMode() ? store.addServer.password : store.editServer.password}
|
||||||
|
placeholder={language.t("dialog.server.add.placeholder")}
|
||||||
|
busy={formBusy()}
|
||||||
|
error={isAddMode() ? store.addServer.error : store.editServer.error}
|
||||||
|
status={isAddMode() ? store.addServer.status : store.editServer.status}
|
||||||
|
onChange={isAddMode() ? handleAddChange : handleEditChange}
|
||||||
|
onNameChange={isAddMode() ? handleAddNameChange : handleEditNameChange}
|
||||||
|
onUsernameChange={isAddMode() ? handleAddUsernameChange : handleEditUsernameChange}
|
||||||
|
onPasswordChange={isAddMode() ? handleAddPasswordChange : handleEditPasswordChange}
|
||||||
|
onSubmit={submitForm}
|
||||||
|
onBack={resetForm}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
<List
|
<List
|
||||||
search={{
|
search={{
|
||||||
placeholder: language.t("dialog.server.search.placeholder"),
|
placeholder: language.t("dialog.server.search.placeholder"),
|
||||||
@@ -400,69 +535,33 @@ export function DialogSelectServer() {
|
|||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (x) select(x)
|
if (x) select(x)
|
||||||
}}
|
}}
|
||||||
onFilter={(value) => {
|
|
||||||
if (value && store.addServer.showForm && !store.addServer.adding) {
|
|
||||||
resetAdd()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
divider={true}
|
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"
|
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"
|
||||||
add={
|
|
||||||
store.addServer.showForm
|
|
||||||
? {
|
|
||||||
render: () => (
|
|
||||||
<AddRow
|
|
||||||
value={store.addServer.url}
|
|
||||||
placeholder={language.t("dialog.server.add.placeholder")}
|
|
||||||
adding={store.addServer.adding}
|
|
||||||
error={store.addServer.error}
|
|
||||||
status={store.addServer.status}
|
|
||||||
onChange={handleAddChange}
|
|
||||||
onKeyDown={handleAddKey}
|
|
||||||
onBlur={blurAdd}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{(i) => {
|
{(i) => {
|
||||||
const key = ServerConnection.key(i)
|
const key = ServerConnection.key(i)
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-3 min-w-0 flex-1 group/item">
|
<div class="flex items-center gap-3 min-w-0 flex-1 w-full group/item">
|
||||||
<Show
|
<div class="flex flex-col h-full items-start w-5">
|
||||||
when={store.editServer.id !== i.http.url}
|
<ServerHealthIndicator health={store.status[key]} />
|
||||||
fallback={
|
</div>
|
||||||
<EditRow
|
|
||||||
value={store.editServer.value}
|
|
||||||
placeholder={language.t("dialog.server.add.placeholder")}
|
|
||||||
busy={store.editServer.busy}
|
|
||||||
error={store.editServer.error}
|
|
||||||
status={store.editServer.status}
|
|
||||||
onChange={handleEditChange}
|
|
||||||
onKeyDown={(event) => handleEditKey(event, i)}
|
|
||||||
onBlur={() => handleEdit(i, store.editServer.value)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ServerRow
|
<ServerRow
|
||||||
conn={i}
|
conn={i}
|
||||||
status={store.status[key]}
|
|
||||||
dimmed={store.status[key]?.healthy === false}
|
dimmed={store.status[key]?.healthy === false}
|
||||||
class="flex items-center gap-3 px-4 min-w-0 flex-1"
|
status={store.status[key]}
|
||||||
|
class="flex items-center gap-3 min-w-0 flex-1"
|
||||||
badge={
|
badge={
|
||||||
<Show when={defaultUrl() === i.http.url}>
|
<Show when={defaultUrl() === i.http.url}>
|
||||||
<span class="text-text-weak bg-surface-base text-14-regular px-1.5 rounded-xs">
|
<span class="text-text-base bg-surface-base text-14-regular px-1.5 rounded-xs">
|
||||||
{language.t("dialog.server.status.default")}
|
{language.t("dialog.server.status.default")}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
}
|
}
|
||||||
|
showCredentials
|
||||||
/>
|
/>
|
||||||
</Show>
|
<div class="flex items-center justify-center gap-4 pl-4">
|
||||||
<Show when={store.editServer.id !== i.http.url}>
|
|
||||||
<div class="flex items-center justify-center gap-5 pl-4">
|
|
||||||
<Show when={ServerConnection.key(current()) === key}>
|
<Show when={ServerConnection.key(current()) === key}>
|
||||||
<p class="text-text-weak text-12-regular">{language.t("dialog.server.current")}</p>
|
<Icon name="check" class="h-6" />
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={i.type === "http"}>
|
<Show when={i.type === "http"}>
|
||||||
@@ -479,12 +578,8 @@ export function DialogSelectServer() {
|
|||||||
<DropdownMenu.Content class="mt-1">
|
<DropdownMenu.Content class="mt-1">
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setStore("editServer", {
|
if (i.type !== "http") return
|
||||||
id: i.http.url,
|
startEdit(i)
|
||||||
value: i.http.url,
|
|
||||||
error: "",
|
|
||||||
status: store.status[ServerConnection.key(i)]?.healthy,
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
||||||
@@ -508,35 +603,42 @@ export function DialogSelectServer() {
|
|||||||
onSelect={() => handleRemove(ServerConnection.key(i))}
|
onSelect={() => handleRemove(ServerConnection.key(i))}
|
||||||
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
||||||
{language.t("dialog.server.menu.delete")}
|
|
||||||
</DropdownMenu.ItemLabel>
|
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</Show>
|
||||||
|
|
||||||
<div class="px-5 pb-5">
|
<div class="px-5 pb-5">
|
||||||
|
<Show
|
||||||
|
when={isFormMode()}
|
||||||
|
fallback={
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon="plus-small"
|
icon="plus-small"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => {
|
onClick={startAdd}
|
||||||
setStore("addServer", { showForm: true, url: "", error: "" })
|
|
||||||
scrollListToBottom()
|
|
||||||
}}
|
|
||||||
class="py-1.5 pl-1.5 pr-3 flex items-center gap-1.5"
|
class="py-1.5 pl-1.5 pr-3 flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
{store.addServer.adding ? language.t("dialog.server.add.checking") : language.t("dialog.server.add.button")}
|
{language.t("dialog.server.add.button")}
|
||||||
</Button>
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button variant="primary" size="large" onClick={submitForm} disabled={formBusy()} class="px-3 py-1.5">
|
||||||
|
{formBusy()
|
||||||
|
? language.t("dialog.server.add.checking")
|
||||||
|
: isAddMode()
|
||||||
|
? language.t("dialog.server.add.button")
|
||||||
|
: language.t("common.save")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { encodeFilePath } from "@/context/file/path"
|
|||||||
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
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 (
|
|
||||||
<Tooltip
|
|
||||||
openDelay={2000}
|
|
||||||
placement="bottom-start"
|
|
||||||
class="w-full"
|
|
||||||
contentStyle={{ "max-width": "480px", width: "fit-content" }}
|
|
||||||
value={
|
|
||||||
<div class="flex items-center min-w-0 whitespace-nowrap text-12-regular">
|
|
||||||
<span
|
|
||||||
class="min-w-0 truncate text-text-invert-base"
|
|
||||||
style={{ direction: "rtl", "unicode-bidi": "plaintext" }}
|
|
||||||
>
|
|
||||||
{prefix}
|
|
||||||
</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">{leaf}</span>
|
|
||||||
<Show when={label}>
|
|
||||||
{(text) => (
|
|
||||||
<>
|
|
||||||
<span class="mx-1 font-bold text-text-invert-strong">•</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">{text()}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<Show when={props.node.type === "directory" && props.node.ignored}>
|
|
||||||
<>
|
|
||||||
<span class="mx-1 font-bold text-text-invert-strong">•</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">Ignored</span>
|
|
||||||
</>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FileTree(props: {
|
export default function FileTree(props: {
|
||||||
path: string
|
path: string
|
||||||
class?: string
|
class?: string
|
||||||
@@ -255,7 +201,6 @@ export default function FileTree(props: {
|
|||||||
modified?: readonly string[]
|
modified?: readonly string[]
|
||||||
kinds?: ReadonlyMap<string, Kind>
|
kinds?: ReadonlyMap<string, Kind>
|
||||||
draggable?: boolean
|
draggable?: boolean
|
||||||
tooltip?: boolean
|
|
||||||
onFileClick?: (file: FileNode) => void
|
onFileClick?: (file: FileNode) => void
|
||||||
|
|
||||||
_filter?: Filter
|
_filter?: Filter
|
||||||
@@ -267,7 +212,6 @@ export default function FileTree(props: {
|
|||||||
const file = useFile()
|
const file = useFile()
|
||||||
const level = props.level ?? 0
|
const level = props.level ?? 0
|
||||||
const draggable = () => props.draggable ?? true
|
const draggable = () => props.draggable ?? true
|
||||||
const tooltip = () => props.tooltip ?? true
|
|
||||||
|
|
||||||
const key = (p: string) =>
|
const key = (p: string) =>
|
||||||
file
|
file
|
||||||
@@ -467,7 +411,6 @@ export default function FileTree(props: {
|
|||||||
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
|
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
|
||||||
>
|
>
|
||||||
<Collapsible.Trigger>
|
<Collapsible.Trigger>
|
||||||
<FileTreeNodeTooltip enabled={tooltip()} node={node} kind={kind()}>
|
|
||||||
<FileTreeNode
|
<FileTreeNode
|
||||||
node={node}
|
node={node}
|
||||||
level={level}
|
level={level}
|
||||||
@@ -481,7 +424,6 @@ export default function FileTree(props: {
|
|||||||
<Icon name={expanded() ? "chevron-down" : "chevron-right"} size="small" />
|
<Icon name={expanded() ? "chevron-down" : "chevron-right"} size="small" />
|
||||||
</div>
|
</div>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
</FileTreeNodeTooltip>
|
|
||||||
</Collapsible.Trigger>
|
</Collapsible.Trigger>
|
||||||
<Collapsible.Content class="relative pt-0.5">
|
<Collapsible.Content class="relative pt-0.5">
|
||||||
<div
|
<div
|
||||||
@@ -504,7 +446,6 @@ export default function FileTree(props: {
|
|||||||
kinds={props.kinds}
|
kinds={props.kinds}
|
||||||
active={props.active}
|
active={props.active}
|
||||||
draggable={props.draggable}
|
draggable={props.draggable}
|
||||||
tooltip={props.tooltip}
|
|
||||||
onFileClick={props.onFileClick}
|
onFileClick={props.onFileClick}
|
||||||
_filter={filter()}
|
_filter={filter()}
|
||||||
_marks={marks()}
|
_marks={marks()}
|
||||||
@@ -517,7 +458,6 @@ export default function FileTree(props: {
|
|||||||
</Collapsible>
|
</Collapsible>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={node.type === "file"}>
|
<Match when={node.type === "file"}>
|
||||||
<FileTreeNodeTooltip enabled={tooltip()} node={node} kind={kind()}>
|
|
||||||
<FileTreeNode
|
<FileTreeNode
|
||||||
node={node}
|
node={node}
|
||||||
level={level}
|
level={level}
|
||||||
@@ -563,7 +503,6 @@ export default function FileTree(props: {
|
|||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
</FileTreeNodeTooltip>
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createEffect, on, Component, Show, onCleanup, Switch, Match, createMemo
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createFocusSignal } from "@solid-primitives/active-element"
|
import { createFocusSignal } from "@solid-primitives/active-element"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
import { useFile } from "@/context/file"
|
import { selectionFromLines, type SelectedLineRange, useFile } from "@/context/file"
|
||||||
import {
|
import {
|
||||||
ContentPart,
|
ContentPart,
|
||||||
DEFAULT_PROMPT,
|
DEFAULT_PROMPT,
|
||||||
@@ -23,7 +23,6 @@ import { Button } from "@opencode-ai/ui/button"
|
|||||||
import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-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 { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
@@ -43,6 +42,9 @@ import {
|
|||||||
canNavigateHistoryAtCursor,
|
canNavigateHistoryAtCursor,
|
||||||
navigatePromptHistory,
|
navigatePromptHistory,
|
||||||
prependHistoryEntry,
|
prependHistoryEntry,
|
||||||
|
type PromptHistoryComment,
|
||||||
|
type PromptHistoryEntry,
|
||||||
|
type PromptHistoryStoredEntry,
|
||||||
promptLength,
|
promptLength,
|
||||||
} from "./prompt-input/history"
|
} from "./prompt-input/history"
|
||||||
import { createPromptSubmit } from "./prompt-input/submit"
|
import { createPromptSubmit } from "./prompt-input/submit"
|
||||||
@@ -170,12 +172,29 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const focus = { file: item.path, id: item.commentID }
|
const focus = { file: item.path, id: item.commentID }
|
||||||
comments.setActive(focus)
|
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))
|
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
|
||||||
if (wantsReview) {
|
if (wantsReview) {
|
||||||
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
||||||
layout.fileTree.setTab("changes")
|
layout.fileTree.setTab("changes")
|
||||||
tabs().setActive("review")
|
tabs().setActive("review")
|
||||||
requestAnimationFrame(() => comments.setFocus(focus))
|
queueCommentFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +202,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
const tab = files.tab(item.path)
|
const tab = files.tab(item.path)
|
||||||
tabs().open(tab)
|
tabs().open(tab)
|
||||||
files.load(item.path)
|
tabs().setActive(tab)
|
||||||
requestAnimationFrame(() => comments.setFocus(focus))
|
Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
|
||||||
}
|
}
|
||||||
|
|
||||||
const recent = createMemo(() => {
|
const recent = createMemo(() => {
|
||||||
@@ -219,7 +238,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
popover: "at" | "slash" | null
|
popover: "at" | "slash" | null
|
||||||
historyIndex: number
|
historyIndex: number
|
||||||
savedPrompt: Prompt | null
|
savedPrompt: PromptHistoryEntry | null
|
||||||
placeholder: number
|
placeholder: number
|
||||||
draggingType: "image" | "@mention" | null
|
draggingType: "image" | "@mention" | null
|
||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
@@ -227,7 +246,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}>({
|
}>({
|
||||||
popover: null,
|
popover: null,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
savedPrompt: null,
|
savedPrompt: null as PromptHistoryEntry | null,
|
||||||
placeholder: Math.floor(Math.random() * EXAMPLES.length),
|
placeholder: Math.floor(Math.random() * EXAMPLES.length),
|
||||||
draggingType: null,
|
draggingType: null,
|
||||||
mode: "normal",
|
mode: "normal",
|
||||||
@@ -256,7 +275,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const [history, setHistory] = persisted(
|
const [history, setHistory] = persisted(
|
||||||
Persist.global("prompt-history", ["prompt-history.v1"]),
|
Persist.global("prompt-history", ["prompt-history.v1"]),
|
||||||
createStore<{
|
createStore<{
|
||||||
entries: Prompt[]
|
entries: PromptHistoryStoredEntry[]
|
||||||
}>({
|
}>({
|
||||||
entries: [],
|
entries: [],
|
||||||
}),
|
}),
|
||||||
@@ -264,7 +283,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const [shellHistory, setShellHistory] = persisted(
|
const [shellHistory, setShellHistory] = persisted(
|
||||||
Persist.global("prompt-history-shell", ["prompt-history-shell.v1"]),
|
Persist.global("prompt-history-shell", ["prompt-history-shell.v1"]),
|
||||||
createStore<{
|
createStore<{
|
||||||
entries: Prompt[]
|
entries: PromptHistoryStoredEntry[]
|
||||||
}>({
|
}>({
|
||||||
entries: [],
|
entries: [],
|
||||||
}),
|
}),
|
||||||
@@ -282,9 +301,66 @@ export const PromptInput: Component<PromptInputProps> = (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)
|
const length = position === "start" ? 0 : promptLength(p)
|
||||||
setStore("applyingHistory", true)
|
setStore("applyingHistory", true)
|
||||||
|
applyHistoryComments(entry.comments)
|
||||||
prompt.set(p, length)
|
prompt.set(p, length)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
editorRef.focus()
|
editorRef.focus()
|
||||||
@@ -846,7 +922,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
|
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
|
||||||
const currentHistory = mode === "shell" ? shellHistory : history
|
const currentHistory = mode === "shell" ? shellHistory : history
|
||||||
const setCurrentHistory = mode === "shell" ? setShellHistory : setHistory
|
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
|
if (next === currentHistory.entries) return
|
||||||
setCurrentHistory("entries", next)
|
setCurrentHistory("entries", next)
|
||||||
}
|
}
|
||||||
@@ -857,12 +933,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
entries: store.mode === "shell" ? shellHistory.entries : history.entries,
|
entries: store.mode === "shell" ? shellHistory.entries : history.entries,
|
||||||
historyIndex: store.historyIndex,
|
historyIndex: store.historyIndex,
|
||||||
currentPrompt: prompt.current(),
|
currentPrompt: prompt.current(),
|
||||||
|
currentComments: historyComments(),
|
||||||
savedPrompt: store.savedPrompt,
|
savedPrompt: store.savedPrompt,
|
||||||
})
|
})
|
||||||
if (!result.handled) return false
|
if (!result.handled) return false
|
||||||
setStore("historyIndex", result.historyIndex)
|
setStore("historyIndex", result.historyIndex)
|
||||||
setStore("savedPrompt", result.savedPrompt)
|
setStore("savedPrompt", result.savedPrompt)
|
||||||
applyHistoryPrompt(result.prompt, result.cursor)
|
applyHistoryPrompt(result.entry, result.cursor)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,6 +1125,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
||||||
|
const accepting = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return false
|
||||||
|
return permission.isAutoAccepting(id, sdk.directory)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
||||||
@@ -1227,41 +1309,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={store.mode === "normal" && permission.permissionsEnabled() && params.id}>
|
|
||||||
<div class="pointer-events-none absolute bottom-2 left-2">
|
<div class="pointer-events-none absolute bottom-2 left-2">
|
||||||
<div class="pointer-events-auto">
|
<div class="pointer-events-auto">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
placement="top"
|
placement="top"
|
||||||
gutter={8}
|
gutter={8}
|
||||||
title={language.t("command.permissions.autoaccept.enable")}
|
title={language.t(
|
||||||
|
accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable",
|
||||||
|
)}
|
||||||
keybind={command.keybind("permissions.autoaccept")}
|
keybind={command.keybind("permissions.autoaccept")}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
data-action="prompt-permissions"
|
data-action="prompt-permissions"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
|
disabled={!params.id}
|
||||||
|
onClick={() => {
|
||||||
|
if (!params.id) return
|
||||||
|
permission.toggleAutoAccept(params.id, sdk.directory)
|
||||||
|
}}
|
||||||
classList={{
|
classList={{
|
||||||
"_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
|
"size-6 flex items-center justify-center": true,
|
||||||
"text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
|
"text-text-base": !accepting(),
|
||||||
"hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
|
"hover:bg-surface-success-base": accepting(),
|
||||||
}}
|
}}
|
||||||
aria-label={
|
aria-label={
|
||||||
permission.isAutoAccepting(params.id!, sdk.directory)
|
accepting()
|
||||||
? language.t("command.permissions.autoaccept.disable")
|
? language.t("command.permissions.autoaccept.disable")
|
||||||
: language.t("command.permissions.autoaccept.enable")
|
: language.t("command.permissions.autoaccept.enable")
|
||||||
}
|
}
|
||||||
aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
|
aria-pressed={accepting()}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="chevron-double-right"
|
name="chevron-double-right"
|
||||||
size="small"
|
size="small"
|
||||||
classList={{ "text-icon-success-base": permission.isAutoAccepting(params.id!, sdk.directory) }}
|
classList={{ "text-icon-success-base": accepting() }}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</DockShellForm>
|
</DockShellForm>
|
||||||
<Show when={store.mode === "normal" || store.mode === "shell"}>
|
<Show when={store.mode === "normal" || store.mode === "shell"}>
|
||||||
@@ -1311,7 +1397,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id={local.model.current()!.provider.id as IconName}
|
id={local.model.current()!.provider.id}
|
||||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
/>
|
/>
|
||||||
@@ -1341,7 +1427,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id={local.model.current()!.provider.id as IconName}
|
id={local.model.current()!.provider.id}
|
||||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ describe("buildRequestParts", () => {
|
|||||||
result.requestParts.some((part) => part.type === "file" && part.url.startsWith("file:///repo/src/foo.ts")),
|
result.requestParts.some((part) => part.type === "file" && part.url.startsWith("file:///repo/src/foo.ts")),
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
expect(result.requestParts.some((part) => part.type === "text" && part.synthetic)).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).toHaveLength(result.requestParts.length)
|
||||||
expect(result.optimisticParts.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_1")).toBe(true)
|
expect(result.optimisticParts.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_1")).toBe(true)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { FileSelection } from "@/context/file"
|
|||||||
import { encodeFilePath } from "@/context/file/path"
|
import { encodeFilePath } from "@/context/file/path"
|
||||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
|
import { createCommentMetadata, formatCommentNote } from "@/utils/comment-note"
|
||||||
|
|
||||||
type PromptRequestPart = (TextPartInput | FilePartInput | AgentPartInput) & { id: string }
|
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 isFileAttachment = (part: Prompt[number]): part is FileAttachmentPart => part.type === "file"
|
||||||
const isAgentAttachment = (part: Prompt[number]): part is AgentPart => part.type === "agent"
|
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 => {
|
const toOptimisticPart = (part: PromptRequestPart, sessionID: string, messageID: string): Part => {
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
return {
|
return {
|
||||||
@@ -153,8 +142,15 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
|
|||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
type: "text",
|
type: "text",
|
||||||
text: commentNote(item.path, item.selection, comment),
|
text: formatCommentNote({ path: item.path, selection: item.selection, comment }),
|
||||||
synthetic: true,
|
synthetic: true,
|
||||||
|
metadata: createCommentMetadata({
|
||||||
|
path: item.path,
|
||||||
|
selection: item.selection,
|
||||||
|
comment,
|
||||||
|
preview: item.preview,
|
||||||
|
origin: item.commentOrigin,
|
||||||
|
}),
|
||||||
} satisfies PromptRequestPart,
|
} satisfies PromptRequestPart,
|
||||||
filePart,
|
filePart,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,25 +3,42 @@ import type { Prompt } from "@/context/prompt"
|
|||||||
import {
|
import {
|
||||||
canNavigateHistoryAtCursor,
|
canNavigateHistoryAtCursor,
|
||||||
clonePromptParts,
|
clonePromptParts,
|
||||||
|
normalizePromptHistoryEntry,
|
||||||
navigatePromptHistory,
|
navigatePromptHistory,
|
||||||
prependHistoryEntry,
|
prependHistoryEntry,
|
||||||
promptLength,
|
promptLength,
|
||||||
|
type PromptHistoryComment,
|
||||||
} from "./history"
|
} from "./history"
|
||||||
|
|
||||||
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
|
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 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", () => {
|
describe("prompt-input history", () => {
|
||||||
test("prependHistoryEntry skips empty prompt and deduplicates consecutive entries", () => {
|
test("prependHistoryEntry skips empty prompt and deduplicates consecutive entries", () => {
|
||||||
const first = prependHistoryEntry([], DEFAULT_PROMPT)
|
const first = prependHistoryEntry([], DEFAULT_PROMPT)
|
||||||
expect(first).toEqual([])
|
expect(first).toEqual([])
|
||||||
|
|
||||||
|
const commentsOnly = prependHistoryEntry([], DEFAULT_PROMPT, [comment("c1")])
|
||||||
|
expect(commentsOnly).toHaveLength(1)
|
||||||
|
|
||||||
const withOne = prependHistoryEntry([], text("hello"))
|
const withOne = prependHistoryEntry([], text("hello"))
|
||||||
expect(withOne).toHaveLength(1)
|
expect(withOne).toHaveLength(1)
|
||||||
|
|
||||||
const deduped = prependHistoryEntry(withOne, text("hello"))
|
const deduped = prependHistoryEntry(withOne, text("hello"))
|
||||||
expect(deduped).toBe(withOne)
|
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", () => {
|
test("navigatePromptHistory restores saved prompt when moving down from newest", () => {
|
||||||
@@ -31,24 +48,57 @@ describe("prompt-input history", () => {
|
|||||||
entries,
|
entries,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
currentPrompt: text("draft"),
|
currentPrompt: text("draft"),
|
||||||
|
currentComments: [comment("draft")],
|
||||||
savedPrompt: null,
|
savedPrompt: null,
|
||||||
})
|
})
|
||||||
expect(up.handled).toBe(true)
|
expect(up.handled).toBe(true)
|
||||||
if (!up.handled) throw new Error("expected handled")
|
if (!up.handled) throw new Error("expected handled")
|
||||||
expect(up.historyIndex).toBe(0)
|
expect(up.historyIndex).toBe(0)
|
||||||
expect(up.cursor).toBe("start")
|
expect(up.cursor).toBe("start")
|
||||||
|
expect(up.entry.comments).toEqual([])
|
||||||
|
|
||||||
const down = navigatePromptHistory({
|
const down = navigatePromptHistory({
|
||||||
direction: "down",
|
direction: "down",
|
||||||
entries,
|
entries,
|
||||||
historyIndex: up.historyIndex,
|
historyIndex: up.historyIndex,
|
||||||
currentPrompt: text("ignored"),
|
currentPrompt: text("ignored"),
|
||||||
|
currentComments: [],
|
||||||
savedPrompt: up.savedPrompt,
|
savedPrompt: up.savedPrompt,
|
||||||
})
|
})
|
||||||
expect(down.handled).toBe(true)
|
expect(down.handled).toBe(true)
|
||||||
if (!down.handled) throw new Error("expected handled")
|
if (!down.handled) throw new Error("expected handled")
|
||||||
expect(down.historyIndex).toBe(-1)
|
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", () => {
|
test("helpers clone prompt and count text content length", () => {
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
import type { Prompt } from "@/context/prompt"
|
import type { Prompt } from "@/context/prompt"
|
||||||
|
import type { SelectedLineRange } from "@/context/file"
|
||||||
|
|
||||||
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
|
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
|
||||||
|
|
||||||
export const MAX_HISTORY = 100
|
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) {
|
export function canNavigateHistoryAtCursor(direction: "up" | "down", text: string, cursor: number, inHistory = false) {
|
||||||
const position = Math.max(0, Math.min(cursor, text.length))
|
const position = Math.max(0, Math.min(cursor, text.length))
|
||||||
const atStart = position === 0
|
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) {
|
export function promptLength(prompt: Prompt) {
|
||||||
return prompt.reduce((len, part) => len + ("content" in part ? part.content.length : 0), 0)
|
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
|
const text = prompt
|
||||||
.map((part) => ("content" in part ? part.content : ""))
|
.map((part) => ("content" in part ? part.content : ""))
|
||||||
.join("")
|
.join("")
|
||||||
.trim()
|
.trim()
|
||||||
const hasImages = prompt.some((part) => part.type === "image")
|
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]
|
const last = entries[0]
|
||||||
if (last && isPromptEqual(last, entry)) return entries
|
if (last && isPromptEqual(last, entry)) return entries
|
||||||
return [entry, ...entries].slice(0, max)
|
return [entry, ...entries].slice(0, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPromptEqual(promptA: Prompt, promptB: Prompt) {
|
function isCommentEqual(commentA: PromptHistoryComment, commentB: PromptHistoryComment) {
|
||||||
if (promptA.length !== promptB.length) return false
|
return (
|
||||||
for (let i = 0; i < promptA.length; i++) {
|
commentA.path === commentB.path &&
|
||||||
const partA = promptA[i]
|
commentA.comment === commentB.comment &&
|
||||||
const partB = promptB[i]
|
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 !== partB.type) return false
|
||||||
if (partA.type === "text" && partA.content !== (partB.type === "text" ? partB.content : "")) return false
|
if (partA.type === "text" && partA.content !== (partB.type === "text" ? partB.content : "")) return false
|
||||||
if (partA.type === "file") {
|
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 === "agent" && partA.name !== (partB.type === "agent" ? partB.name : "")) return false
|
||||||
if (partA.type === "image" && partA.id !== (partB.type === "image" ? partB.id : "")) 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryNavInput = {
|
type HistoryNavInput = {
|
||||||
direction: "up" | "down"
|
direction: "up" | "down"
|
||||||
entries: Prompt[]
|
entries: PromptHistoryStoredEntry[]
|
||||||
historyIndex: number
|
historyIndex: number
|
||||||
currentPrompt: Prompt
|
currentPrompt: Prompt
|
||||||
savedPrompt: Prompt | null
|
currentComments: PromptHistoryComment[]
|
||||||
|
savedPrompt: PromptHistoryEntry | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryNavResult =
|
type HistoryNavResult =
|
||||||
| {
|
| {
|
||||||
handled: false
|
handled: false
|
||||||
historyIndex: number
|
historyIndex: number
|
||||||
savedPrompt: Prompt | null
|
savedPrompt: PromptHistoryEntry | null
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
handled: true
|
handled: true
|
||||||
historyIndex: number
|
historyIndex: number
|
||||||
savedPrompt: Prompt | null
|
savedPrompt: PromptHistoryEntry | null
|
||||||
prompt: Prompt
|
entry: PromptHistoryEntry
|
||||||
cursor: "start" | "end"
|
cursor: "start" | "end"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,22 +181,27 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (input.historyIndex === -1) {
|
if (input.historyIndex === -1) {
|
||||||
|
const entry = normalizePromptHistoryEntry(input.entries[0])
|
||||||
return {
|
return {
|
||||||
handled: true,
|
handled: true,
|
||||||
historyIndex: 0,
|
historyIndex: 0,
|
||||||
savedPrompt: clonePromptParts(input.currentPrompt),
|
savedPrompt: {
|
||||||
prompt: input.entries[0],
|
prompt: clonePromptParts(input.currentPrompt),
|
||||||
|
comments: clonePromptHistoryComments(input.currentComments),
|
||||||
|
},
|
||||||
|
entry,
|
||||||
cursor: "start",
|
cursor: "start",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.historyIndex < input.entries.length - 1) {
|
if (input.historyIndex < input.entries.length - 1) {
|
||||||
const next = input.historyIndex + 1
|
const next = input.historyIndex + 1
|
||||||
|
const entry = normalizePromptHistoryEntry(input.entries[next])
|
||||||
return {
|
return {
|
||||||
handled: true,
|
handled: true,
|
||||||
historyIndex: next,
|
historyIndex: next,
|
||||||
savedPrompt: input.savedPrompt,
|
savedPrompt: input.savedPrompt,
|
||||||
prompt: input.entries[next],
|
entry,
|
||||||
cursor: "start",
|
cursor: "start",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,11 +215,12 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
|
|||||||
|
|
||||||
if (input.historyIndex > 0) {
|
if (input.historyIndex > 0) {
|
||||||
const next = input.historyIndex - 1
|
const next = input.historyIndex - 1
|
||||||
|
const entry = normalizePromptHistoryEntry(input.entries[next])
|
||||||
return {
|
return {
|
||||||
handled: true,
|
handled: true,
|
||||||
historyIndex: next,
|
historyIndex: next,
|
||||||
savedPrompt: input.savedPrompt,
|
savedPrompt: input.savedPrompt,
|
||||||
prompt: input.entries[next],
|
entry,
|
||||||
cursor: "end",
|
cursor: "end",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +231,7 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
|
|||||||
handled: true,
|
handled: true,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
savedPrompt: null,
|
savedPrompt: null,
|
||||||
prompt: input.savedPrompt,
|
entry: input.savedPrompt,
|
||||||
cursor: "end",
|
cursor: "end",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +240,10 @@ export function navigatePromptHistory(input: HistoryNavInput): HistoryNavResult
|
|||||||
handled: true,
|
handled: true,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
savedPrompt: null,
|
savedPrompt: null,
|
||||||
|
entry: {
|
||||||
prompt: DEFAULT_PROMPT,
|
prompt: DEFAULT_PROMPT,
|
||||||
|
comments: [],
|
||||||
|
},
|
||||||
cursor: "end",
|
cursor: "end",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import {
|
import {
|
||||||
|
children,
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
@@ -9,7 +10,7 @@ import {
|
|||||||
type ParentProps,
|
type ParentProps,
|
||||||
Show,
|
Show,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { type ServerConnection, serverDisplayName } from "@/context/server"
|
import { type ServerConnection, serverName } from "@/context/server"
|
||||||
import type { ServerHealth } from "@/utils/server-health"
|
import type { ServerHealth } from "@/utils/server-health"
|
||||||
|
|
||||||
interface ServerRowProps extends ParentProps {
|
interface ServerRowProps extends ParentProps {
|
||||||
@@ -20,13 +21,14 @@ interface ServerRowProps extends ParentProps {
|
|||||||
versionClass?: string
|
versionClass?: string
|
||||||
dimmed?: boolean
|
dimmed?: boolean
|
||||||
badge?: JSXElement
|
badge?: JSXElement
|
||||||
|
showCredentials?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServerRow(props: ServerRowProps) {
|
export function ServerRow(props: ServerRowProps) {
|
||||||
const [truncated, setTruncated] = createSignal(false)
|
const [truncated, setTruncated] = createSignal(false)
|
||||||
let nameRef: HTMLSpanElement | undefined
|
let nameRef: HTMLSpanElement | undefined
|
||||||
let versionRef: HTMLSpanElement | undefined
|
let versionRef: HTMLSpanElement | undefined
|
||||||
const name = createMemo(() => serverDisplayName(props.conn))
|
const name = createMemo(() => serverName(props.conn))
|
||||||
|
|
||||||
const check = () => {
|
const check = () => {
|
||||||
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
|
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
|
||||||
@@ -52,35 +54,71 @@ export function ServerRow(props: ServerRowProps) {
|
|||||||
|
|
||||||
const tooltipValue = () => (
|
const tooltipValue = () => (
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
<span>{name()}</span>
|
<span>{serverName(props.conn, true)}</span>
|
||||||
<Show when={props.status?.version}>
|
<Show when={props.status?.version}>
|
||||||
<span class="text-text-invert-base">{props.status?.version}</span>
|
<span class="text-text-invert-weak">v{props.status?.version}</span>
|
||||||
</Show>
|
</Show>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const badge = children(() => props.badge)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip value={tooltipValue()} placement="top" inactive={!truncated()}>
|
<Tooltip
|
||||||
|
class="flex-1"
|
||||||
|
value={tooltipValue()}
|
||||||
|
placement="top-start"
|
||||||
|
inactive={!truncated() && !props.conn.displayName}
|
||||||
|
>
|
||||||
<div class={props.class} classList={{ "opacity-50": props.dimmed }}>
|
<div class={props.class} classList={{ "opacity-50": props.dimmed }}>
|
||||||
<div
|
<div class="flex flex-col items-start">
|
||||||
classList={{
|
<div class="flex flex-row items-center gap-2">
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
|
||||||
"bg-icon-success-base": props.status?.healthy === true,
|
|
||||||
"bg-icon-critical-base": props.status?.healthy === false,
|
|
||||||
"bg-border-weak-base": props.status === undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span ref={nameRef} class={props.nameClass ?? "truncate"}>
|
<span ref={nameRef} class={props.nameClass ?? "truncate"}>
|
||||||
{name()}
|
{name()}
|
||||||
</span>
|
</span>
|
||||||
|
<Show
|
||||||
|
when={badge()}
|
||||||
|
fallback={
|
||||||
<Show when={props.status?.version}>
|
<Show when={props.status?.version}>
|
||||||
<span ref={versionRef} class={props.versionClass ?? "text-text-weak text-14-regular truncate"}>
|
<span ref={versionRef} class={props.versionClass ?? "text-text-weak text-14-regular truncate"}>
|
||||||
{props.status?.version}
|
v{props.status?.version}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
{props.badge}
|
}
|
||||||
|
>
|
||||||
|
{(badge) => badge()}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={props.showCredentials && props.conn.type === "http" && props.conn}>
|
||||||
|
{(conn) => (
|
||||||
|
<div class="flex flex-row gap-3">
|
||||||
|
<span>
|
||||||
|
{conn().http.username ? (
|
||||||
|
<span class="text-text-weak">{conn().http.username}</span>
|
||||||
|
) : (
|
||||||
|
<span class="text-text-weaker">no username</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{conn().http.password && <span class="text-text-weak">••••••••</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ServerHealthIndicator(props: { health?: ServerHealth }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
|
"bg-icon-success-base": props.health?.healthy === true,
|
||||||
|
"bg-icon-critical-base": props.health?.healthy === false,
|
||||||
|
"bg-border-weak-base": props.health === undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { same } from "@/utils/same"
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Accordion } from "@opencode-ai/ui/accordion"
|
import { Accordion } from "@opencode-ai/ui/accordion"
|
||||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
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 { Markdown } from "@opencode-ai/ui/markdown"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||||
@@ -47,7 +47,8 @@ function RawMessageContent(props: { message: Message; getParts: (id: string) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Code
|
<File
|
||||||
|
mode="text"
|
||||||
file={file()}
|
file={file()}
|
||||||
overflow="wrap"
|
overflow="wrap"
|
||||||
class="select-text"
|
class="select-text"
|
||||||
|
|||||||
@@ -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 { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Portal } from "solid-js/web"
|
import { Portal } from "solid-js/web"
|
||||||
import { useParams } from "@solidjs/router"
|
|
||||||
import { useLayout } from "@/context/layout"
|
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
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"
|
import { StatusPopover } from "../status-popover"
|
||||||
|
|
||||||
const OPEN_APPS = [
|
const OPEN_APPS = [
|
||||||
@@ -35,6 +35,7 @@ const OPEN_APPS = [
|
|||||||
"terminal",
|
"terminal",
|
||||||
"iterm2",
|
"iterm2",
|
||||||
"ghostty",
|
"ghostty",
|
||||||
|
"warp",
|
||||||
"xcode",
|
"xcode",
|
||||||
"android-studio",
|
"android-studio",
|
||||||
"powershell",
|
"powershell",
|
||||||
@@ -45,32 +46,68 @@ type OpenApp = (typeof OPEN_APPS)[number]
|
|||||||
type OS = "macos" | "windows" | "linux" | "unknown"
|
type OS = "macos" | "windows" | "linux" | "unknown"
|
||||||
|
|
||||||
const MAC_APPS = [
|
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: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
|
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
|
||||||
{ id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
|
{ 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: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
|
||||||
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
|
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
|
||||||
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
|
{ 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: "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
|
] as const
|
||||||
|
|
||||||
const WINDOWS_APPS = [
|
const WINDOWS_APPS = [
|
||||||
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
||||||
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
{ 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
|
] as const
|
||||||
|
|
||||||
const LINUX_APPS = [
|
const LINUX_APPS = [
|
||||||
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
||||||
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
{ 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
|
] as const
|
||||||
|
|
||||||
type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
|
type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
|
||||||
@@ -213,7 +250,9 @@ export function SessionHeader() {
|
|||||||
const view = createMemo(() => layout.view(sessionKey))
|
const view = createMemo(() => layout.view(sessionKey))
|
||||||
const os = createMemo(() => detectOS(platform))
|
const os = createMemo(() => detectOS(platform))
|
||||||
|
|
||||||
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ finder: true })
|
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({
|
||||||
|
finder: true,
|
||||||
|
})
|
||||||
|
|
||||||
const apps = createMemo(() => {
|
const apps = createMemo(() => {
|
||||||
if (os() === "macos") return MAC_APPS
|
if (os() === "macos") return MAC_APPS
|
||||||
@@ -259,18 +298,34 @@ export function SessionHeader() {
|
|||||||
|
|
||||||
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
|
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
|
||||||
const [menu, setMenu] = createStore({ open: false })
|
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 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])
|
||||||
|
const opening = createMemo(() => openRequest.app !== undefined)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const value = prefs.app
|
||||||
|
if (options().some((o) => o.id === value)) return
|
||||||
|
setPrefs("app", options()[0]?.id ?? "finder")
|
||||||
|
})
|
||||||
|
|
||||||
const openDir = (app: OpenApp) => {
|
const openDir = (app: OpenApp) => {
|
||||||
|
if (opening() || !canOpen() || !platform.openPath) return
|
||||||
const directory = projectDirectory()
|
const directory = projectDirectory()
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
if (!canOpen()) return
|
|
||||||
|
|
||||||
const item = options().find((o) => o.id === app)
|
const item = options().find((o) => o.id === app)
|
||||||
const openWith = item && "openWith" in item ? item.openWith : undefined
|
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 = () => {
|
const copyPath = () => {
|
||||||
@@ -315,7 +370,9 @@ export function SessionHeader() {
|
|||||||
<div class="flex min-w-0 flex-1 items-center gap-1.5 overflow-visible">
|
<div class="flex min-w-0 flex-1 items-center gap-1.5 overflow-visible">
|
||||||
<Icon name="magnifying-glass" size="small" class="icon-base shrink-0 size-4" />
|
<Icon name="magnifying-glass" size="small" class="icon-base shrink-0 size-4" />
|
||||||
<span class="flex-1 min-w-0 text-12-regular text-text-weak truncate text-left">
|
<span class="flex-1 min-w-0 text-12-regular text-text-weak truncate text-left">
|
||||||
{language.t("session.header.search.placeholder", { project: name() })}
|
{language.t("session.header.search.placeholder", {
|
||||||
|
project: name(),
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -357,14 +414,23 @@ export function SessionHeader() {
|
|||||||
<div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
|
<div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none"
|
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none disabled:!cursor-default"
|
||||||
|
classList={{
|
||||||
|
"bg-surface-raised-base-active": opening(),
|
||||||
|
}}
|
||||||
onClick={() => openDir(current().id)}
|
onClick={() => openDir(current().id)}
|
||||||
|
disabled={opening()}
|
||||||
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
||||||
>
|
>
|
||||||
<div class="flex size-5 shrink-0 items-center justify-center">
|
<div class="flex size-5 shrink-0 items-center justify-center">
|
||||||
<AppIcon id={current().icon} class="size-4" />
|
<Show
|
||||||
|
when={opening()}
|
||||||
|
fallback={<AppIcon id={current().icon} class={openIconSize(current().icon)} />}
|
||||||
|
>
|
||||||
|
<Spinner class="size-3.5 text-icon-base" />
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-12-regular text-text-strong">Open</span>
|
<span class="text-12-regular text-text-strong">{language.t("common.open")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div class="self-stretch w-px bg-border-weak-base" />
|
<div class="self-stretch w-px bg-border-weak-base" />
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
@@ -377,7 +443,11 @@ export function SessionHeader() {
|
|||||||
as={IconButton}
|
as={IconButton}
|
||||||
icon="chevron-down"
|
icon="chevron-down"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-hover"
|
disabled={opening()}
|
||||||
|
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
|
||||||
|
classList={{
|
||||||
|
"bg-surface-raised-base-active": opening(),
|
||||||
|
}}
|
||||||
aria-label={language.t("session.header.open.menu")}
|
aria-label={language.t("session.header.open.menu")}
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
@@ -395,6 +465,7 @@ export function SessionHeader() {
|
|||||||
{(o) => (
|
{(o) => (
|
||||||
<DropdownMenu.RadioItem
|
<DropdownMenu.RadioItem
|
||||||
value={o.id}
|
value={o.id}
|
||||||
|
disabled={opening()}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setMenu("open", false)
|
setMenu("open", false)
|
||||||
openDir(o.id)
|
openDir(o.id)
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ import { useCommand } from "@/context/command"
|
|||||||
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
|
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-x-1.5 min-w-0">
|
<div class="flex items-center gap-x-1.5 min-w-0">
|
||||||
<FileIcon
|
<Show
|
||||||
node={{ path: props.path, type: "file" }}
|
when={!props.active}
|
||||||
classList={{
|
fallback={<FileIcon node={{ path: props.path, type: "file" }} class="size-4 shrink-0" />}
|
||||||
"grayscale-100 group-data-[selected]/tab:grayscale-0": !props.active,
|
>
|
||||||
"grayscale-0": props.active,
|
<span class="relative inline-flex size-4 shrink-0">
|
||||||
}}
|
<FileIcon node={{ path: props.path, type: "file" }} class="absolute inset-0 size-4 tab-fileicon-color" />
|
||||||
/>
|
<FileIcon node={{ path: props.path, type: "file" }} mono class="absolute inset-0 size-4 tab-fileicon-mono" />
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
<span class="text-14-medium truncate">{getFilename(props.path)}</span>
|
<span class="text-14-medium truncate">{getFilename(props.path)}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -37,8 +39,8 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
|
|||||||
return <FileVisual path={value} />
|
return <FileVisual path={value} />
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
<div use:sortable class="h-full flex items-center" classList={{ "opacity-0": sortable.isActiveDraggable }}>
|
||||||
<div class="relative h-full">
|
<div class="relative">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value={props.tab}
|
value={props.tab}
|
||||||
closeButton={
|
closeButton={
|
||||||
@@ -46,6 +48,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
|
|||||||
title={language.t("common.closeTab")}
|
title={language.t("common.closeTab")}
|
||||||
keybind={command.keybind("tab.close")}
|
keybind={command.keybind("tab.close")}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
gutter={10}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="close-small"
|
icon="close-small"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Switch } from "@opencode-ai/ui/switch"
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
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 { type Component, For, Show } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useModels } from "@/context/models"
|
import { useModels } from "@/context/models"
|
||||||
@@ -98,7 +97,7 @@ export const SettingsModels: Component = () => {
|
|||||||
{(group) => (
|
{(group) => (
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class="flex items-center gap-2 pb-2">
|
<div class="flex items-center gap-2 pb-2">
|
||||||
<ProviderIcon id={group.category as IconName} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={group.category} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong">{group.items[0].provider.name}</span>
|
<span class="text-14-medium text-text-strong">{group.items[0].provider.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-surface-raised-base px-4 rounded-lg">
|
<div class="bg-surface-raised-base px-4 rounded-lg">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Tag } from "@opencode-ai/ui/tag"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
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 { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||||
import { createMemo, type Component, For, Show } from "solid-js"
|
import { createMemo, type Component, For, Show } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -33,11 +32,6 @@ export const SettingsProviders: Component = () => {
|
|||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
|
|
||||||
const icon = (id: string): IconName => {
|
|
||||||
if (iconNames.includes(id as IconName)) return id as IconName
|
|
||||||
return "synthetic"
|
|
||||||
}
|
|
||||||
|
|
||||||
const connected = createMemo(() => {
|
const connected = createMemo(() => {
|
||||||
return providers
|
return providers
|
||||||
.connected()
|
.connected()
|
||||||
@@ -154,7 +148,7 @@ export const SettingsProviders: Component = () => {
|
|||||||
{(item) => (
|
{(item) => (
|
||||||
<div class="group flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
<div class="group flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={item.id} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
|
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
|
||||||
<Tag>{type(item)}</Tag>
|
<Tag>{type(item)}</Tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +156,7 @@ export const SettingsProviders: Component = () => {
|
|||||||
when={canDisconnect(item)}
|
when={canDisconnect(item)}
|
||||||
fallback={
|
fallback={
|
||||||
<span class="text-14-regular text-text-base opacity-0 group-hover:opacity-100 transition-opacity duration-200 pr-3 cursor-default">
|
<span class="text-14-regular text-text-base opacity-0 group-hover:opacity-100 transition-opacity duration-200 pr-3 cursor-default">
|
||||||
Connected from your environment variables
|
{language.t("settings.providers.connected.environmentDescription")}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -185,11 +179,24 @@ export const SettingsProviders: Component = () => {
|
|||||||
<div class="flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
<div class="flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
||||||
<div class="flex flex-col min-w-0">
|
<div class="flex flex-col min-w-0">
|
||||||
<div class="flex items-center gap-x-3">
|
<div class="flex items-center gap-x-3">
|
||||||
<ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id={item.id} class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong">{item.name}</span>
|
<span class="text-14-medium text-text-strong">{item.name}</span>
|
||||||
|
<Show when={item.id === "opencode"}>
|
||||||
|
<span class="text-14-regular text-text-weak">
|
||||||
|
{language.t("dialog.provider.opencode.tagline")}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
<Show when={item.id === "opencode"}>
|
<Show when={item.id === "opencode"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={item.id === "opencode-go"}>
|
||||||
|
<>
|
||||||
|
<span class="text-14-regular text-text-weak">
|
||||||
|
{language.t("dialog.provider.opencodeGo.tagline")}
|
||||||
|
</span>
|
||||||
|
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||||
|
</>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={note(item.id)}>
|
<Show when={note(item.id)}>
|
||||||
{(key) => <span class="text-12-regular text-text-weak pl-8">{language.t(key())}</span>}
|
{(key) => <span class="text-12-regular text-text-weak pl-8">{language.t(key())}</span>}
|
||||||
@@ -215,11 +222,13 @@ export const SettingsProviders: Component = () => {
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col min-w-0">
|
<div class="flex flex-col min-w-0">
|
||||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||||
<ProviderIcon id={icon("synthetic")} class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong">Custom provider</span>
|
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
|
||||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-12-regular text-text-weak pl-8">Add an OpenAI-compatible provider by base URL.</span>
|
<span class="text-12-regular text-text-weak pl-8">
|
||||||
|
{language.t("settings.providers.custom.description")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { showToast } from "@opencode-ai/ui/toast"
|
|||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { type Accessor, createEffect, createMemo, createSignal, For, type JSXElement, onCleanup, Show } from "solid-js"
|
import { type Accessor, createEffect, createMemo, createSignal, For, type JSXElement, onCleanup, Show } from "solid-js"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
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 { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
@@ -276,10 +276,11 @@ export function StatusPopover() {
|
|||||||
navigate("/")
|
navigate("/")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ServerHealthIndicator health={health[key]} />
|
||||||
<ServerRow
|
<ServerRow
|
||||||
conn={s}
|
conn={s}
|
||||||
status={health[key]}
|
|
||||||
dimmed={isBlocked()}
|
dimmed={isBlocked()}
|
||||||
|
status={health[key]}
|
||||||
class="flex items-center gap-2 w-full min-w-0"
|
class="flex items-center gap-2 w-full min-w-0"
|
||||||
nameClass="text-14-regular text-text-base truncate"
|
nameClass="text-14-regular text-text-base truncate"
|
||||||
versionClass="text-12-regular text-text-weak truncate"
|
versionClass="text-12-regular text-text-weak truncate"
|
||||||
|
|||||||
@@ -150,4 +150,37 @@ describe("comments session indexing", () => {
|
|||||||
dispose()
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,6 +44,37 @@ function aggregate(comments: Record<string, LineComment[]>) {
|
|||||||
.sort((a, b) => a.time - b.time)
|
.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<Record<string, LineComment[]>>((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<CommentStore>, setStore: SetStoreFunction<CommentStore>) {
|
function createCommentSessionState(store: Store<CommentStore>, setStore: SetStoreFunction<CommentStore>) {
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
focus: null as CommentFocus | null,
|
focus: null as CommentFocus | null,
|
||||||
@@ -70,6 +101,7 @@ function createCommentSessionState(store: Store<CommentStore>, setStore: SetStor
|
|||||||
id: uuid(),
|
id: uuid(),
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
...input,
|
...input,
|
||||||
|
selection: cloneSelection(input.selection),
|
||||||
}
|
}
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
@@ -87,6 +119,23 @@ function createCommentSessionState(store: Store<CommentStore>, 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 = () => {
|
const clear = () => {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore("comments", reconcile({}))
|
setStore("comments", reconcile({}))
|
||||||
@@ -100,6 +149,8 @@ function createCommentSessionState(store: Store<CommentStore>, setStore: SetStor
|
|||||||
all,
|
all,
|
||||||
add,
|
add,
|
||||||
remove,
|
remove,
|
||||||
|
update,
|
||||||
|
replace,
|
||||||
clear,
|
clear,
|
||||||
focus: () => state.focus,
|
focus: () => state.focus,
|
||||||
setFocus,
|
setFocus,
|
||||||
@@ -132,6 +183,8 @@ function createCommentSession(dir: string, id: string | undefined) {
|
|||||||
all: session.all,
|
all: session.all,
|
||||||
add: session.add,
|
add: session.add,
|
||||||
remove: session.remove,
|
remove: session.remove,
|
||||||
|
update: session.update,
|
||||||
|
replace: session.replace,
|
||||||
clear: session.clear,
|
clear: session.clear,
|
||||||
focus: session.focus,
|
focus: session.focus,
|
||||||
setFocus: session.setFocus,
|
setFocus: session.setFocus,
|
||||||
@@ -176,6 +229,8 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
|
|||||||
all: () => session().all(),
|
all: () => session().all(),
|
||||||
add: (input: Omit<LineComment, "id" | "time">) => session().add(input),
|
add: (input: Omit<LineComment, "id" | "time">) => session().add(input),
|
||||||
remove: (file: string, id: string) => session().remove(file, id),
|
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(),
|
clear: () => session().clear(),
|
||||||
focus: () => session().focus(),
|
focus: () => session().focus(),
|
||||||
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
|
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ describe("file path helpers", () => {
|
|||||||
|
|
||||||
test("normalizes Windows absolute paths with mixed separators", () => {
|
test("normalizes Windows absolute paths with mixed separators", () => {
|
||||||
const path = createPathHelpers(() => "C:\\repo")
|
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("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("file://C:/repo/src/app.ts")).toBe("src/app.ts")
|
||||||
expect(path.normalize("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", () => {
|
test("keeps query/hash stripping behavior stable", () => {
|
||||||
|
|||||||
@@ -103,32 +103,30 @@ export function encodeFilePath(filepath: string): string {
|
|||||||
|
|
||||||
export function createPathHelpers(scope: () => string) {
|
export function createPathHelpers(scope: () => string) {
|
||||||
const normalize = (input: string) => {
|
const normalize = (input: string) => {
|
||||||
const root = scope().replace(/\\/g, "/")
|
const root = scope()
|
||||||
|
|
||||||
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))).replace(/\\/g, "/")
|
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
|
||||||
|
|
||||||
// Remove initial root prefix, if it's a complete match or followed by /
|
// Separator-agnostic prefix stripping for Cygwin/native Windows compatibility
|
||||||
// (don't want /foo/bar to root of /f).
|
// Only case-insensitive on Windows (drive letter or UNC paths)
|
||||||
// For Windows paths, also check for case-insensitive match.
|
const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\")
|
||||||
const windows = /^[A-Za-z]:/.test(root)
|
const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/")
|
||||||
const canonRoot = windows ? root.toLowerCase() : root
|
const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/")
|
||||||
const canonPath = windows ? path.toLowerCase() : path
|
|
||||||
if (
|
if (
|
||||||
canonPath.startsWith(canonRoot) &&
|
canonPath.startsWith(canonRoot) &&
|
||||||
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath.startsWith(canonRoot + "/"))
|
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/")
|
||||||
) {
|
) {
|
||||||
// If we match canonRoot + "/", the slash will be removed below.
|
// Slice from original path to preserve native separators
|
||||||
path = path.slice(root.length)
|
path = path.slice(root.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith("./")) {
|
if (path.startsWith("./") || path.startsWith(".\\")) {
|
||||||
path = path.slice(2)
|
path = path.slice(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/") || path.startsWith("\\")) {
|
||||||
path = path.slice(1)
|
path = path.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const MAX_FILE_VIEW_SESSIONS = 20
|
|||||||
const MAX_VIEW_FILES = 500
|
const MAX_VIEW_FILES = 500
|
||||||
|
|
||||||
function normalizeSelectedLines(range: SelectedLineRange): SelectedLineRange {
|
function normalizeSelectedLines(range: SelectedLineRange): SelectedLineRange {
|
||||||
if (range.start <= range.end) return range
|
if (range.start <= range.end) return { ...range }
|
||||||
|
|
||||||
const startSide = range.side
|
const startSide = range.side
|
||||||
const endSide = range.endSide ?? startSide
|
const endSide = range.endSide ?? startSide
|
||||||
|
|||||||
@@ -49,9 +49,12 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
let queue: Queued[] = []
|
let queue: Queued[] = []
|
||||||
let buffer: Queued[] = []
|
let buffer: Queued[] = []
|
||||||
const coalesced = new Map<string, number>()
|
const coalesced = new Map<string, number>()
|
||||||
|
const staleDeltas = new Set<string>()
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
let last = 0
|
let last = 0
|
||||||
|
|
||||||
|
const deltaKey = (directory: string, messageID: string, partID: string) => `${directory}:${messageID}:${partID}`
|
||||||
|
|
||||||
const key = (directory: string, payload: Event) => {
|
const key = (directory: string, payload: Event) => {
|
||||||
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`
|
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`
|
||||||
if (payload.type === "lsp.updated") return `lsp.updated:${directory}`
|
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
|
if (queue.length === 0) return
|
||||||
|
|
||||||
const events = queue
|
const events = queue
|
||||||
|
const skip = staleDeltas.size > 0 ? new Set(staleDeltas) : undefined
|
||||||
queue = buffer
|
queue = buffer
|
||||||
buffer = events
|
buffer = events
|
||||||
queue.length = 0
|
queue.length = 0
|
||||||
coalesced.clear()
|
coalesced.clear()
|
||||||
|
staleDeltas.clear()
|
||||||
|
|
||||||
last = Date.now()
|
last = Date.now()
|
||||||
batch(() => {
|
batch(() => {
|
||||||
for (const event of events) {
|
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)
|
emitter.emit(event.directory, event.payload)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -144,6 +153,10 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
|||||||
const i = coalesced.get(k)
|
const i = coalesced.get(k)
|
||||||
if (i !== undefined) {
|
if (i !== undefined) {
|
||||||
queue[i] = { directory, payload }
|
queue[i] = { directory, payload }
|
||||||
|
if (payload.type === "message.part.updated") {
|
||||||
|
const part = payload.properties.part
|
||||||
|
staleDeltas.add(deltaKey(directory, part.messageID, part.id))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
coalesced.set(k, queue.length)
|
coalesced.set(k, queue.length)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import type { ProjectMeta } from "./global-sync/types"
|
|||||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||||
import { sanitizeProject } from "./global-sync/utils"
|
import { sanitizeProject } from "./global-sync/utils"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -51,12 +52,6 @@ type GlobalStore = {
|
|||||||
reload: undefined | "pending" | "complete"
|
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() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
@@ -207,8 +202,12 @@ function createGlobalSync() {
|
|||||||
console.error("Failed to load sessions", err)
|
console.error("Failed to load sessions", err)
|
||||||
const project = getFilename(directory)
|
const project = getFilename(directory)
|
||||||
showToast({
|
showToast({
|
||||||
|
variant: "error",
|
||||||
title: language.t("toast.session.listFailed.title", { project }),
|
title: language.t("toast.session.listFailed.title", { project }),
|
||||||
description: errorMessage(err),
|
description: formatServerError(err, {
|
||||||
|
unknown: language.t("error.chain.unknown"),
|
||||||
|
invalidConfiguration: language.t("error.server.invalidConfiguration"),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -238,6 +237,8 @@ function createGlobalSync() {
|
|||||||
setStore: child[1],
|
setStore: child[1],
|
||||||
vcsCache: cache,
|
vcsCache: cache,
|
||||||
loadSessions,
|
loadSessions,
|
||||||
|
unknownError: language.t("error.chain.unknown"),
|
||||||
|
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@@ -312,6 +313,9 @@ function createGlobalSync() {
|
|||||||
url: globalSDK.url,
|
url: globalSDK.url,
|
||||||
}),
|
}),
|
||||||
requestFailedTitle: language.t("common.requestFailed"),
|
requestFailedTitle: language.t("common.requestFailed"),
|
||||||
|
unknownError: language.t("error.chain.unknown"),
|
||||||
|
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
|
||||||
|
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||||
setGlobalStore,
|
setGlobalStore,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { batch } from "solid-js"
|
|||||||
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeProviderList } from "./utils"
|
import { cmp, normalizeProviderList } from "./utils"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -35,6 +36,9 @@ export async function bootstrapGlobal(input: {
|
|||||||
connectErrorTitle: string
|
connectErrorTitle: string
|
||||||
connectErrorDescription: string
|
connectErrorDescription: string
|
||||||
requestFailedTitle: string
|
requestFailedTitle: string
|
||||||
|
unknownError: string
|
||||||
|
invalidConfigurationError: string
|
||||||
|
formatMoreCount: (count: number) => string
|
||||||
setGlobalStore: SetStoreFunction<GlobalStore>
|
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||||
}) {
|
}) {
|
||||||
const health = await input.globalSDK.global
|
const health = await input.globalSDK.global
|
||||||
@@ -87,8 +91,11 @@ export async function bootstrapGlobal(input: {
|
|||||||
const results = await Promise.allSettled(tasks)
|
const results = await Promise.allSettled(tasks)
|
||||||
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
|
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
const message = errors[0] instanceof Error ? errors[0].message : String(errors[0])
|
const message = formatServerError(errors[0], {
|
||||||
const more = errors.length > 1 ? ` (+${errors.length - 1} more)` : ""
|
unknown: input.unknownError,
|
||||||
|
invalidConfiguration: input.invalidConfigurationError,
|
||||||
|
})
|
||||||
|
const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: input.requestFailedTitle,
|
title: input.requestFailedTitle,
|
||||||
@@ -115,6 +122,8 @@ export async function bootstrapDirectory(input: {
|
|||||||
setStore: SetStoreFunction<State>
|
setStore: SetStoreFunction<State>
|
||||||
vcsCache: VcsCache
|
vcsCache: VcsCache
|
||||||
loadSessions: (directory: string) => Promise<void> | void
|
loadSessions: (directory: string) => Promise<void> | void
|
||||||
|
unknownError: string
|
||||||
|
invalidConfigurationError: string
|
||||||
}) {
|
}) {
|
||||||
if (input.store.status !== "complete") input.setStore("status", "loading")
|
if (input.store.status !== "complete") input.setStore("status", "loading")
|
||||||
|
|
||||||
@@ -133,8 +142,14 @@ export async function bootstrapDirectory(input: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to bootstrap instance", err)
|
console.error("Failed to bootstrap instance", err)
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
showToast({
|
||||||
showToast({ title: `Failed to reload ${project}`, description: message })
|
variant: "error",
|
||||||
|
title: `Failed to reload ${project}`,
|
||||||
|
description: formatServerError(err, {
|
||||||
|
unknown: input.unknownError,
|
||||||
|
invalidConfiguration: input.invalidConfigurationError,
|
||||||
|
}),
|
||||||
|
})
|
||||||
input.setStore("status", "partial")
|
input.setStore("status", "partial")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { dict as no } from "@/i18n/no"
|
|||||||
import { dict as br } from "@/i18n/br"
|
import { dict as br } from "@/i18n/br"
|
||||||
import { dict as th } from "@/i18n/th"
|
import { dict as th } from "@/i18n/th"
|
||||||
import { dict as bs } from "@/i18n/bs"
|
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 uiEn } from "@opencode-ai/ui/i18n/en"
|
||||||
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
||||||
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
|
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 uiBr } from "@opencode-ai/ui/i18n/br"
|
||||||
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
|
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
|
||||||
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
|
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
|
||||||
|
import { dict as uiTr } from "@opencode-ai/ui/i18n/tr"
|
||||||
|
|
||||||
export type Locale =
|
export type Locale =
|
||||||
| "en"
|
| "en"
|
||||||
@@ -53,6 +55,7 @@ export type Locale =
|
|||||||
| "br"
|
| "br"
|
||||||
| "th"
|
| "th"
|
||||||
| "bs"
|
| "bs"
|
||||||
|
| "tr"
|
||||||
|
|
||||||
type RawDictionary = typeof en & typeof uiEn
|
type RawDictionary = typeof en & typeof uiEn
|
||||||
type Dictionary = i18n.Flatten<RawDictionary>
|
type Dictionary = i18n.Flatten<RawDictionary>
|
||||||
@@ -78,6 +81,7 @@ const LOCALES: readonly Locale[] = [
|
|||||||
"no",
|
"no",
|
||||||
"br",
|
"br",
|
||||||
"th",
|
"th",
|
||||||
|
"tr",
|
||||||
]
|
]
|
||||||
|
|
||||||
const LABEL_KEY: Record<Locale, keyof Dictionary> = {
|
const LABEL_KEY: Record<Locale, keyof Dictionary> = {
|
||||||
@@ -97,6 +101,7 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = {
|
|||||||
br: "language.br",
|
br: "language.br",
|
||||||
th: "language.th",
|
th: "language.th",
|
||||||
bs: "language.bs",
|
bs: "language.bs",
|
||||||
|
tr: "language.tr",
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = i18n.flatten({ ...en, ...uiEn })
|
const base = i18n.flatten({ ...en, ...uiEn })
|
||||||
@@ -117,6 +122,7 @@ const DICT: Record<Locale, Dictionary> = {
|
|||||||
br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
|
br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
|
||||||
th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
|
th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
|
||||||
bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
|
bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
|
||||||
|
tr: { ...base, ...i18n.flatten({ ...tr, ...uiTr }) },
|
||||||
}
|
}
|
||||||
|
|
||||||
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
|
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
|
||||||
@@ -138,6 +144,7 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
|
|||||||
{ locale: "br", match: (language) => language.startsWith("pt") },
|
{ locale: "br", match: (language) => language.startsWith("pt") },
|
||||||
{ locale: "th", match: (language) => language.startsWith("th") },
|
{ locale: "th", match: (language) => language.startsWith("th") },
|
||||||
{ locale: "bs", match: (language) => language.startsWith("bs") },
|
{ locale: "bs", match: (language) => language.startsWith("bs") },
|
||||||
|
{ locale: "tr", match: (language) => language.startsWith("tr") },
|
||||||
]
|
]
|
||||||
|
|
||||||
type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
|
type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
|
||||||
@@ -157,6 +164,7 @@ const PARITY_CHECK: Record<Exclude<Locale, "en">, Record<ParityKey, string>> = {
|
|||||||
br,
|
br,
|
||||||
th,
|
th,
|
||||||
bs,
|
bs,
|
||||||
|
tr,
|
||||||
}
|
}
|
||||||
void PARITY_CHECK
|
void PARITY_CHECK
|
||||||
|
|
||||||
|
|||||||
@@ -41,4 +41,24 @@ describe("createScrollPersistence", () => {
|
|||||||
vi.useRealTimers()
|
vi.useRealTimers()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("reseeds empty cache after persisted snapshot loads", () => {
|
||||||
|
const snapshot = {
|
||||||
|
session: {},
|
||||||
|
} as Record<string, Record<string, { x: number; y: number }>>
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,8 +33,16 @@ export function createScrollPersistence(opts: Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seed(sessionKey: string) {
|
function seed(sessionKey: string) {
|
||||||
if (cache[sessionKey]) return
|
const next = clone(opts.getSnapshot(sessionKey))
|
||||||
setCache(sessionKey, 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) {
|
function scroll(sessionKey: string, tab: string) {
|
||||||
|
|||||||
@@ -132,6 +132,28 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
|
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
|
||||||
|
|
||||||
const meta = { pruned: false, disposed: false }
|
const meta = { pruned: false, disposed: false }
|
||||||
|
const idleSuppressMs = 5000
|
||||||
|
const idleSuppressedUntilBySession = new Map<string, number>()
|
||||||
|
|
||||||
|
const idleSuppressKey = (directory: string, sessionID?: string) => {
|
||||||
|
if (!sessionID) return
|
||||||
|
return `${directory}:${sessionID}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const markIdleSuppressed = (directory: string, sessionID?: string) => {
|
||||||
|
const key = idleSuppressKey(directory, sessionID)
|
||||||
|
if (!key) return
|
||||||
|
idleSuppressedUntilBySession.set(key, Date.now() + idleSuppressMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldSuppressIdle = (directory: string, sessionID?: string) => {
|
||||||
|
const key = idleSuppressKey(directory, sessionID)
|
||||||
|
if (!key) return false
|
||||||
|
const until = idleSuppressedUntilBySession.get(key)
|
||||||
|
if (until === undefined) return false
|
||||||
|
idleSuppressedUntilBySession.delete(key)
|
||||||
|
return until > Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
const updateUnseen = (scope: "session" | "project", key: string, unseen: Notification[]) => {
|
const updateUnseen = (scope: "session" | "project", key: string, unseen: Notification[]) => {
|
||||||
setIndex(scope, "unseen", key, unseen)
|
setIndex(scope, "unseen", key, unseen)
|
||||||
@@ -290,12 +312,15 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
if (event.type !== "session.idle" && event.type !== "session.error") return
|
if (event.type !== "session.idle" && event.type !== "session.error") return
|
||||||
|
|
||||||
const directory = e.name
|
const directory = e.name
|
||||||
const time = Date.now()
|
if (event.type === "session.error") {
|
||||||
|
markIdleSuppressed(directory, event.properties.sessionID)
|
||||||
|
}
|
||||||
if (event.type === "session.idle") {
|
if (event.type === "session.idle") {
|
||||||
handleSessionIdle(directory, event, time)
|
if (shouldSuppressIdle(directory, event.properties.sessionID)) return
|
||||||
|
handleSessionIdle(directory, event, Date.now())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleSessionError(directory, event, time)
|
handleSessionError(directory, event, Date.now())
|
||||||
})
|
})
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
meta.disposed = true
|
meta.disposed = true
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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 } 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<PermissionRequest, "sessionID">
|
||||||
|
|
||||||
|
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 auto-accept 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(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
|
||||||
|
export function acceptKey(sessionID: string, directory?: string) {
|
||||||
|
if (!directory) return sessionID
|
||||||
|
return `${base64Encode(directory)}/${sessionID}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function accepted(autoAccept: Record<string, boolean>, sessionID: string, directory?: string) {
|
||||||
|
const key = acceptKey(sessionID, directory)
|
||||||
|
return autoAccept[key] ?? autoAccept[sessionID]
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, string>())
|
||||||
|
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<string, boolean>,
|
||||||
|
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 ?? true
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import { Persist, persisted } from "@/utils/persist"
|
|||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
|
import { acceptKey, autoRespondsPermission } from "./permission-auto-respond"
|
||||||
|
|
||||||
type PermissionRespondFn = (input: {
|
type PermissionRespondFn = (input: {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
@@ -16,10 +16,6 @@ type PermissionRespondFn = (input: {
|
|||||||
directory?: string
|
directory?: string
|
||||||
}) => void
|
}) => void
|
||||||
|
|
||||||
function shouldAutoAccept(perm: PermissionRequest) {
|
|
||||||
return perm.permission === "edit"
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNonAllowRule(rule: unknown) {
|
function isNonAllowRule(rule: unknown) {
|
||||||
if (!rule) return false
|
if (!rule) return false
|
||||||
if (typeof rule === "string") return rule !== "allow"
|
if (typeof rule === "string") return rule !== "allow"
|
||||||
@@ -40,10 +36,7 @@ function hasPermissionPromptRules(permission: unknown) {
|
|||||||
if (Array.isArray(permission)) return false
|
if (Array.isArray(permission)) return false
|
||||||
|
|
||||||
const config = permission as Record<string, unknown>
|
const config = permission as Record<string, unknown>
|
||||||
if (isNonAllowRule(config.edit)) return true
|
return Object.values(config).some(isNonAllowRule)
|
||||||
if (isNonAllowRule(config.write)) return true
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
|
export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
|
||||||
@@ -61,9 +54,25 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
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<string, unknown>
|
||||||
|
if (data.autoAccept) return value
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
autoAccept:
|
||||||
|
typeof data.autoAcceptEdits === "object" && data.autoAcceptEdits && !Array.isArray(data.autoAcceptEdits)
|
||||||
|
? data.autoAcceptEdits
|
||||||
|
: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
createStore({
|
createStore({
|
||||||
autoAcceptEdits: {} as Record<string, boolean>,
|
autoAccept: {} as Record<string, boolean>,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,14 +114,14 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptKey(sessionID: string, directory?: string) {
|
function isAutoAccepting(sessionID: string, directory?: string) {
|
||||||
if (!directory) return sessionID
|
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
|
||||||
return `${base64Encode(directory)}/${sessionID}`
|
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAutoAccepting(sessionID: string, directory?: string) {
|
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
|
||||||
const key = acceptKey(sessionID, directory)
|
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
|
||||||
return store.autoAcceptEdits[key] ?? store.autoAcceptEdits[sessionID] ?? false
|
return autoRespondsPermission(store.autoAccept, session, permission, directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
function bumpEnableVersion(sessionID: string, directory?: string) {
|
function bumpEnableVersion(sessionID: string, directory?: string) {
|
||||||
@@ -127,8 +136,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
if (event?.type !== "permission.asked") return
|
if (event?.type !== "permission.asked") return
|
||||||
|
|
||||||
const perm = event.properties
|
const perm = event.properties
|
||||||
if (!isAutoAccepting(perm.sessionID, e.name)) return
|
if (!shouldAutoRespond(perm, e.name)) return
|
||||||
if (!shouldAutoAccept(perm)) return
|
|
||||||
|
|
||||||
respondOnce(perm, e.name)
|
respondOnce(perm, e.name)
|
||||||
})
|
})
|
||||||
@@ -139,8 +147,8 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
const version = bumpEnableVersion(sessionID, directory)
|
const version = bumpEnableVersion(sessionID, directory)
|
||||||
setStore(
|
setStore(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
draft.autoAcceptEdits[key] = true
|
draft.autoAccept[key] = true
|
||||||
delete draft.autoAcceptEdits[sessionID]
|
delete draft.autoAccept[sessionID]
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,8 +159,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
if (!isAutoAccepting(sessionID, directory)) return
|
if (!isAutoAccepting(sessionID, directory)) return
|
||||||
for (const perm of x.data ?? []) {
|
for (const perm of x.data ?? []) {
|
||||||
if (!perm?.id) continue
|
if (!perm?.id) continue
|
||||||
if (perm.sessionID !== sessionID) continue
|
if (!shouldAutoRespond(perm, directory)) continue
|
||||||
if (!shouldAutoAccept(perm)) continue
|
|
||||||
respondOnce(perm, directory)
|
respondOnce(perm, directory)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -161,11 +168,12 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
|
|
||||||
function disable(sessionID: string, directory?: string) {
|
function disable(sessionID: string, directory?: string) {
|
||||||
bumpEnableVersion(sessionID, directory)
|
bumpEnableVersion(sessionID, directory)
|
||||||
const key = directory ? acceptKey(sessionID, directory) : undefined
|
const key = directory ? acceptKey(sessionID, directory) : sessionID
|
||||||
setStore(
|
setStore(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
if (key) delete draft.autoAcceptEdits[key]
|
draft.autoAccept[key] = false
|
||||||
delete draft.autoAcceptEdits[sessionID]
|
if (!directory) return
|
||||||
|
delete draft.autoAccept[sessionID]
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -174,7 +182,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
|
|||||||
ready,
|
ready,
|
||||||
respond,
|
respond,
|
||||||
autoResponds(permission: PermissionRequest, directory?: string) {
|
autoResponds(permission: PermissionRequest, directory?: string) {
|
||||||
return isAutoAccepting(permission.sessionID, directory) && shouldAutoAccept(permission)
|
return shouldAutoRespond(permission, directory)
|
||||||
},
|
},
|
||||||
isAutoAccepting,
|
isAutoAccepting,
|
||||||
toggleAutoAccept(sessionID: string, directory: string) {
|
toggleAutoAccept(sessionID: string, directory: string) {
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ function contextItemKey(item: ContextItem) {
|
|||||||
return `${key}:c=${digest.slice(0, 8)}`
|
return `${key}:c=${digest.slice(0, 8)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCommentItem(item: ContextItem | (ContextItem & { key: string })) {
|
||||||
|
return item.type === "file" && !!item.comment?.trim()
|
||||||
|
}
|
||||||
|
|
||||||
function createPromptActions(
|
function createPromptActions(
|
||||||
setStore: SetStoreFunction<{
|
setStore: SetStoreFunction<{
|
||||||
prompt: Prompt
|
prompt: Prompt
|
||||||
@@ -189,6 +193,26 @@ function createPromptSession(dir: string, id: string | undefined) {
|
|||||||
remove(key: string) {
|
remove(key: string) {
|
||||||
setStore("context", "items", (items) => items.filter((x) => x.key !== key))
|
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<FileContextItem> & { 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,
|
set: actions.set,
|
||||||
reset: actions.reset,
|
reset: actions.reset,
|
||||||
@@ -251,6 +275,10 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
|
|||||||
items: () => session().context.items(),
|
items: () => session().context.items(),
|
||||||
add: (item: ContextItem) => session().context.add(item),
|
add: (item: ContextItem) => session().context.add(item),
|
||||||
remove: (key: string) => session().context.remove(key),
|
remove: (key: string) => session().context.remove(key),
|
||||||
|
removeComment: (path: string, commentID: string) => session().context.removeComment(path, commentID),
|
||||||
|
updateComment: (path: string, commentID: string, next: Partial<FileContextItem> & { comment?: string }) =>
|
||||||
|
session().context.updateComment(path, commentID, next),
|
||||||
|
replaceComments: (items: FileContextItem[]) => session().context.replaceComments(items),
|
||||||
},
|
},
|
||||||
set: (prompt: Prompt, cursorPosition?: number) => session().set(prompt, cursorPosition),
|
set: (prompt: Prompt, cursorPosition?: number) => session().set(prompt, cursorPosition),
|
||||||
reset: () => session().reset(),
|
reset: () => session().reset(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Persist, persisted } from "@/utils/persist"
|
|||||||
import { checkServerHealth } from "@/utils/server-health"
|
import { checkServerHealth } from "@/utils/server-health"
|
||||||
|
|
||||||
type StoredProject = { worktree: string; expanded: boolean }
|
type StoredProject = { worktree: string; expanded: boolean }
|
||||||
|
type StoredServer = string | ServerConnection.HttpBase | ServerConnection.Http
|
||||||
const HEALTH_POLL_INTERVAL_MS = 10_000
|
const HEALTH_POLL_INTERVAL_MS = 10_000
|
||||||
|
|
||||||
export function normalizeServerUrl(input: string) {
|
export function normalizeServerUrl(input: string) {
|
||||||
@@ -15,9 +16,9 @@ export function normalizeServerUrl(input: string) {
|
|||||||
return withProtocol.replace(/\/+$/, "")
|
return withProtocol.replace(/\/+$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serverDisplayName(conn?: ServerConnection.Any) {
|
export function serverName(conn?: ServerConnection.Any, ignoreDisplayName = false) {
|
||||||
if (!conn) return ""
|
if (!conn) return ""
|
||||||
if (conn.displayName) return conn.displayName
|
if (conn.displayName && !ignoreDisplayName) return conn.displayName
|
||||||
return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
|
return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,22 +101,33 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
|||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
Persist.global("server", ["server.v3"]),
|
Persist.global("server", ["server.v3"]),
|
||||||
createStore({
|
createStore({
|
||||||
list: [] as string[],
|
list: [] as StoredServer[],
|
||||||
projects: {} as Record<string, StoredProject[]>,
|
projects: {} as Record<string, StoredProject[]>,
|
||||||
lastProject: {} as Record<string, string>,
|
lastProject: {} as Record<string, string>,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const url = (x: StoredServer) => (typeof x === "string" ? x : "type" in x ? x.http.url : x.url)
|
||||||
|
|
||||||
const allServers = createMemo((): Array<ServerConnection.Any> => {
|
const allServers = createMemo((): Array<ServerConnection.Any> => {
|
||||||
const servers = [
|
const servers = [
|
||||||
...(props.servers ?? []),
|
...(props.servers ?? []),
|
||||||
...store.list.map((value) => ({
|
...store.list.map((value) =>
|
||||||
|
typeof value === "string"
|
||||||
|
? {
|
||||||
type: "http" as const,
|
type: "http" as const,
|
||||||
http: typeof value === "string" ? { url: value } : value,
|
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()]
|
return [...deduped.values()]
|
||||||
})
|
})
|
||||||
@@ -156,27 +168,29 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
|||||||
if (state.active !== input) setState("active", input)
|
if (state.active !== input) setState("active", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(input: string) {
|
function add(input: ServerConnection.Http) {
|
||||||
const url = normalizeServerUrl(input)
|
const url_ = normalizeServerUrl(input.http.url)
|
||||||
if (!url) return
|
if (!url_) return
|
||||||
|
const conn = { ...input, http: { ...input.http, url: url_ } }
|
||||||
return batch(() => {
|
return batch(() => {
|
||||||
const http: ServerConnection.HttpBase = { url }
|
const existing = store.list.findIndex((x) => url(x) === url_)
|
||||||
if (!store.list.includes(url)) {
|
if (existing !== -1) {
|
||||||
setStore("list", store.list.length, url)
|
setStore("list", existing, conn)
|
||||||
|
} else {
|
||||||
|
setStore("list", store.list.length, conn)
|
||||||
}
|
}
|
||||||
const conn: ServerConnection.Http = { type: "http", http }
|
|
||||||
setState("active", ServerConnection.key(conn))
|
setState("active", ServerConnection.key(conn))
|
||||||
return conn
|
return conn
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(key: ServerConnection.Key) {
|
function remove(key: ServerConnection.Key) {
|
||||||
const list = store.list.filter((x) => x !== key)
|
const list = store.list.filter((x) => url(x) !== key)
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore("list", list)
|
setStore("list", list)
|
||||||
if (state.active === key) {
|
if (state.active === key) {
|
||||||
const next = list[0]
|
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
|
return state.active
|
||||||
},
|
},
|
||||||
get name() {
|
get name() {
|
||||||
return serverDisplayName(current())
|
return serverName(current())
|
||||||
},
|
},
|
||||||
get list() {
|
get list() {
|
||||||
return allServers()
|
return allServers()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user