Compare commits

..
Author SHA1 Message Date
vimtor 418cbd4d4e fix(core): persist rotated org credentials 2026-09-01 11:56:00 +02:00
vimtor 50fa545458 chore: sync v2 CI fixes 2026-09-01 11:42:47 +02:00
vimtor 4d8b040cfd test(core): avoid Bun URL error wording 2026-09-01 11:21:14 +02:00
vimtor e9e0e5a4d7 fix(core): honor device token organization 2026-09-01 11:12:30 +02:00
596 changed files with 17907 additions and 17869 deletions
+2 -1
View File
@@ -417,6 +417,7 @@ jobs:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
if: github.ref_name == 'beta'
with:
name: opencode-preview-cli
path: packages/cli/dist
@@ -479,7 +480,7 @@ jobs:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
OPENCODE_CLI_DIST: ${{ github.workspace }}/packages/cli/dist
OPENCODE_CLI_DIST: ${{ (github.ref_name == 'beta' && format('{0}/packages/cli/dist', github.workspace)) || '' }}
- name: Build
run: bun run build
+1 -1
View File
@@ -3,7 +3,7 @@
- Current implementation changes belong in `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
- This repository does not use Changesets. Do not add `.changeset` files; follow the existing release workflow instead.
- The default branch in this repo is `v2`.
- Default new branches and worktrees to `v2`, or `origin/v2` when the local `v2` ref is unavailable, and default pull requests to target `v2`. Use another base or target branch when the requester explicitly instructs it.
- Base all new branches and worktrees on `v2`, or `origin/v2` when the local `v2` ref is unavailable. Do not base them on `dev`.
- Local `main` ref may not exist; use `v2` or `origin/v2` for diffs.
## Live V2 TUI Testing
+31 -23
View File
@@ -418,7 +418,7 @@
"electron-window-state": "^5.0.3",
},
"devDependencies": {
"@brendonovich/vite-plugin-opencode": "0.1.3",
"@brendonovich/vite-plugin-opencode": "0.1.1",
"@effect/platform-node": "catalog:",
"@lydell/node-pty": "catalog:",
"@opencode-ai/app": "workspace:*",
@@ -592,8 +592,8 @@
},
"peerDependencies": {
"@opencode-ai/theme": "workspace:*",
"@opentui/core": ">=0.5.10",
"@opentui/solid": ">=0.5.10",
"@opentui/core": ">=0.5.9",
"@opentui/solid": ">=0.5.9",
"solid-js": ">=1.9.0",
},
"optionalPeers": [
@@ -730,7 +730,6 @@
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"vite": "catalog:",
"vite-plugin-solid": "catalog:",
},
},
"packages/simulation": {
@@ -1050,7 +1049,6 @@
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"solid-js@1.9.15": "patches/solid-js@1.9.15.patch",
"vite@8.2.2": "patches/vite@8.2.2.patch",
"@ff-labs/fff-bun@0.10.5": "patches/@ff-labs%2Ffff-bun@0.10.5.patch",
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
@@ -1080,9 +1078,9 @@
"@npmcli/arborist": "9.4.0",
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opentui/core": "0.5.10",
"@opentui/keymap": "0.5.10",
"@opentui/solid": "0.5.10",
"@opentui/core": "0.5.9",
"@opentui/keymap": "0.5.9",
"@opentui/solid": "0.5.9",
"@pierre/diffs": "1.2.10",
"@playwright/test": "1.59.1",
"@sentry/solid": "10.71.0",
@@ -1597,7 +1595,7 @@
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
"@brendonovich/vite-plugin-opencode": ["@brendonovich/vite-plugin-opencode@0.1.3", "", { "dependencies": { "@babel/core": "^7.29.0", "@opencode-ai/client": "0.0.0-beta-18050" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-iiIwlNoycOMUiaUzL1ZirLapCG3WY8cg9hSj4KgE/JYIWgqauoHikIlfUt7SdMqqGIkb4iZu6I1MxgDDJaRfBA=="],
"@brendonovich/vite-plugin-opencode": ["@brendonovich/vite-plugin-opencode@0.1.1", "", { "dependencies": { "@babel/core": "^7.29.0", "@opencode-ai/client": "0.0.0-beta-18050" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-aPG0ct8ctxAqndbNOx7NW0GhU6QY6sOUfi/DaKqH9c5WdxICSsUop6uSkJwPDHP9WpN9eg0dd2D2qwYpG6UdHw=="],
"@bruits/satteri-darwin-arm64": ["@bruits/satteri-darwin-arm64@0.9.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iw4nZgx9v30lWo/MTngQqi1pI78KI0DnkSm+lVJGYdmPLgAyDNJigVhpG42/Iq55A6c1Ll8q66ljyyRiQUxwow=="],
@@ -2223,27 +2221,27 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
"@opentui/core": ["@opentui/core@0.5.10", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.10", "@opentui/core-darwin-x64": "0.5.10", "@opentui/core-linux-arm64": "0.5.10", "@opentui/core-linux-arm64-musl": "0.5.10", "@opentui/core-linux-x64": "0.5.10", "@opentui/core-linux-x64-musl": "0.5.10", "@opentui/core-win32-arm64": "0.5.10", "@opentui/core-win32-x64": "0.5.10" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-C3a2UbmefeAjIxAgm4BqjuSxKT4oqutfvYFwVvUgMxmGRHkNbBc/s7sukV0JgwcxFcV3uMFrXxo+E+BQtvuOiw=="],
"@opentui/core": ["@opentui/core@0.5.9", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.9", "@opentui/core-darwin-x64": "0.5.9", "@opentui/core-linux-arm64": "0.5.9", "@opentui/core-linux-arm64-musl": "0.5.9", "@opentui/core-linux-x64": "0.5.9", "@opentui/core-linux-x64-musl": "0.5.9", "@opentui/core-win32-arm64": "0.5.9", "@opentui/core-win32-x64": "0.5.9" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-d0EWYyp6djitu1N1R0o75NrLl4TxY3oJEmRNrX9vSKKC5/jriGQdSV6lJmwiB77O0cxtBnPzWztAV79vA1J2fA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Vyb+nTbhab8ZcRy5gg1loEEGwRcIbjAeVRIBfHBcbFDqmITBOg7x2gqJ+x/TnoOy4uwMhCmICUN2wiyREw3r1Q=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YcpWGD8GwoO3UYYw0kLDI4qof3ElSwWW5M3fNG+Kw87BSjCa5frZqak5xebdZ/XU5Xsk+BjgbBsxbzo9yrIjZQ=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tTFLcM7Oj1gTyhm/bUdAt3C6grZdCxPk6+/g2azcZBUlI3/62LwbeRS6HbQKFFmm+1fUmX8cq6kWrtul885mVg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-xgdZwgcwlDCqi0WPCS1d0GVSd+oKDFX+UVE1ZPei8u8W3pirXGSExxVHNku7QbEYCRvpCeMtoKIa6xN9cVTsoQ=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-ncJXcgudhBf2GdJyF3xVQN/Ec+1F7GOL+pRrURmgBYSj2v1w6EyoDQFAACtPTK2c3R38W6fvZwL4JSLlm4EFXQ=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-vHBiJp69wMaL+fRMDpnXCCIpPyQRPZEWCWBatnblUUGIWUseqeQlaPow6ljHSbgAd/GjygAf4tFCyTqNT2ezZA=="],
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-dGMphDKexSdeYqwl0wgoFBP88Ta/cdi1Zc1mk29/ENkSCGz+74zlCHgqTHRNGLmI8W5TfuUtCyktQH11/Z+TBQ=="],
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-A7GW4NCTInj2vy3X2gVO2Sm8jllgdLxwPdOklPqyl+xqivQtyG9qwS5YXoCk9fBJt10ORhYKETEjYEnIE5xvMQ=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.10", "", { "os": "linux", "cpu": "x64" }, "sha512-5qtYaOgwVycZD1GaGshTRsi0rXPAmVExO03N1JQaHu+NYxK/vXSOc7Bu4QW0sPXx3Sp0SpzpP+FHjXABfoK66g=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-rdw7QVopVHwj6fMrJc7hPGVaYg2V5NBlW3MUfkrQBlUlo/87OC4aYDAh5oRIL1CIFD9LjDdh7hwmUNbn/K9Pcw=="],
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.10", "", { "os": "linux", "cpu": "x64" }, "sha512-Oj4H9hApuvuTKPWxh4SoZAgGJorR7vbvnrZA/cAkSMAk2VGSoHRRcqeXQbcH8IcdjVZ0KFpv8Zkl/D5Ye+2mew=="],
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-J4wQs1OMPZ4hR93Op1C/BLFmIta2mUJm4M7djevlgcWcal4RSNO4V8QvUXfogWGci+ADZqEmX2osQQDY5NVJpw=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-A9VhgvTxQoUdZ+8LmUumEng1sQNbj9QQQT3NYG9mSxI54qTANi7vOWNSphMiY6RMVsr22pgm6nUvSSvJXv7Jog=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-OnmEsGLWwsHrm6sTsWSSesu4LGjSV1JNWqCsEItbuitWaqH6rXdSahSc5jMFsS57bZxUyaRz01yAqiZzUSXnVQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.10", "", { "os": "win32", "cpu": "x64" }, "sha512-u3KHa7kEeWrmKVDRJYpxSGO+g5E9cMGlrmTsPN3GVPHUmQMiREUawLXUvsU8+IHaQnqG3Q5nuE1yf4fPBzS+Qw=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/CnAIfKL7+ZeGLyZoXV5zS71Nd8Zn97RUir2DAIY05MJozmfg5s7XOAdUNYuK1cg5mbwlAuqeAsfbuWUAEe15g=="],
"@opentui/keymap": ["@opentui/keymap@0.5.10", "", { "dependencies": { "@opentui/core": "0.5.10" }, "peerDependencies": { "@opentui/react": "0.5.10", "@opentui/solid": "0.5.10", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-8vDJF+ltXscSnLEv3rgCa4m7PcoYZeUT9BngugpFCmVoNevbaRtYijjdfiUuLmXfT61lO5QbR6nEhn2RZMK8ow=="],
"@opentui/keymap": ["@opentui/keymap@0.5.9", "", { "dependencies": { "@opentui/core": "0.5.9" }, "peerDependencies": { "@opentui/react": "0.5.9", "@opentui/solid": "0.5.9", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-ZcRNeuCDv+LJ89BS5xBw90e0S1Etn6j41jflg6LZlWj4bnqjCU55dq0+zERUdkkC4rqD4SikM8q7V3ZfkwzyyQ=="],
"@opentui/solid": ["@opentui/solid@0.5.10", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.10", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-KrmMIsHiKBHOABTC0brOwqWm+sGq1ZX2sGCAx6WgtBbE3STMup9n8TAy/6gUYhwcjC9zugT53ytfSVwCwVWZUg=="],
"@opentui/solid": ["@opentui/solid@0.5.9", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.9", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-zGSP/ia9ww+TTMvQMZjJW2+h7fg9YkVRIM723EgvP+v7ZBZ/0vMzPNh7FJ0jET3ysw0h5hsbvcYISs4sUcf+UA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -3215,6 +3213,8 @@
"@upstash/redis": ["@upstash/redis@1.38.0", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg=="],
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
"@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@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=="],
@@ -5453,6 +5453,8 @@
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"sury": ["sury@11.0.0-alpha.4", "", { "peerDependencies": { "rescript": "12.x" }, "optionalPeers": ["rescript"] }, "sha512-oeG/GJWZvQCKtGPpLbu0yCZudfr5LxycDo5kh7SJmKHDPCsEPJssIZL2Eb4Tl7g9aPEvIDuRrkS+L0pybsMEMA=="],
"svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
@@ -5667,6 +5669,8 @@
"uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="],
"valibot": ["valibot@1.4.2", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg=="],
"validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
@@ -5815,7 +5819,9 @@
"xml-naming": ["xml-naming@0.3.0", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="],
"xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
@@ -5847,6 +5853,8 @@
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
"zod-openapi": ["zod-openapi@5.4.6", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-P2jsOOBAq/6hCwUsMCjUATZ8szkMsV5VAwZENfyxp2Hc/XPJQpVwAgevWZc65xZauCwWB9LAn7zYeiCJFAEL+A=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
"zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="],
@@ -6257,8 +6265,6 @@
"@opencode-ai/session-ui/vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.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-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"@opencode-ai/session-ui/vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="],
"@opencode-ai/stats-app/vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.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-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"@opencode-ai/storybook/@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
@@ -6603,6 +6609,8 @@
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"postcss-css-variables/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"postcss-css-variables/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
-5
View File
@@ -87,11 +87,6 @@ stdenv.mkDerivation (finalAttrs: {
cd packages/desktop
export OPENCODE_CLI_DIST="$TMPDIR/desktop-cli"
cli_package=$(bun -e 'import { getCurrentCli } from "./scripts/utils.ts"; console.log(getCurrentCli().package.replace("@opencode-ai/", ""))')
mkdir -p "$OPENCODE_CLI_DIST/$cli_package/bin"
cp ${lib.getExe opencode} "$OPENCODE_CLI_DIST/$cli_package/bin/opencode2"
bun run build
npx electron-builder --dir \
--config electron-builder.config.ts \
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-Vazzj75ji5YbLtX5X0q+IZvygGrfmEi3+PzJsAkaa3s=",
"aarch64-linux": "sha256-QFKzH7wlRcvYG4EHQgQ+++9zNQ+Mw5DVKembGiMjiCg=",
"aarch64-darwin": "sha256-WAYBBA2jLW95wM8Dgk77KsO3yS5efxQs9oBVAcdx8Gc=",
"x86_64-darwin": "sha256-c9P+VQvfdOu5ef6BnKXb3/IUo8pPFaYQv8B+ijofm1s="
"x86_64-linux": "sha256-nV2bI91uUqHJugt1mERgYNCn/zqWJ21YXc5YozwQ+Ss=",
"aarch64-linux": "sha256-s/0PghIWeRHsMw9Re84sQ8qW5IZwyhXrhmYlUy2xkt4=",
"aarch64-darwin": "sha256-FAm7Bk3NPikHYmmJFwB+0V3saRZqlyxDHuM8gasN3zA=",
"x86_64-darwin": "sha256-kdYwYQhubvO9CtVsuHsGnIJ9/byWQ5FMFCLRoScQ614="
}
}
+4 -5
View File
@@ -49,9 +49,9 @@
"@octokit/rest": "22.0.0",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@opentui/core": "0.5.10",
"@opentui/keymap": "0.5.10",
"@opentui/solid": "0.5.10",
"@opentui/core": "0.5.9",
"@opentui/keymap": "0.5.9",
"@opentui/solid": "0.5.9",
"@tanstack/solid-virtual": "3.13.37",
"@shikijs/stream": "4.4.3",
"@standard-schema/spec": "1.1.0",
@@ -178,7 +178,6 @@
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
"@tanstack/virtual-core@3.17.8": "patches/@tanstack%2Fvirtual-core@3.17.8.patch",
"@ff-labs/fff-bun@0.10.5": "patches/@ff-labs%2Ffff-bun@0.10.5.patch",
"vite@8.2.2": "patches/vite@8.2.2.patch"
"@ff-labs/fff-bun@0.10.5": "patches/@ff-labs%2Ffff-bun@0.10.5.patch"
}
}
+3 -6
View File
@@ -1257,12 +1257,9 @@ const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (
const events: LLMEvent[] = []
if (event.type === "response.completed") {
for (const item of event.response?.output ?? []) {
if (
item.type !== "function_call" ||
!item.call_id ||
!Object.values(current.tools).some((tool) => tool?.id === item.call_id)
)
continue
const id = item.id ?? (item.type === "function_call" ? item.call_id : undefined)
if (id === undefined) continue
if (item.type !== "function_call" || !current.tools[id]) continue
const [next, emitted] = yield* onOutputItemDone(current, item)
current = next
events.push(...emitted)
@@ -4017,7 +4017,7 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("uses completed response output when item completion and its terminal item id are missing", () =>
it.effect("uses completed response output when output item completion is missing", () =>
Effect.gen(function* () {
const body = sseEvents(
{
@@ -4032,6 +4032,7 @@ describe("OpenAI Responses route", () => {
output: [
{
type: "function_call",
id: "fc_item_1",
call_id: "call_1",
name: "lookup",
arguments: '{"query":"weather"}',
@@ -11,23 +11,6 @@ story("raises the docked composer only in dark mode", async ({ mount, page }) =>
await expect(composer).toHaveCSS("background-color", "rgb(36, 36, 36)")
})
story("centers add menu shortcuts in a consistent column", async ({ mount, page }) => {
const component = await mount("opencode-composer-flow--empty-draft")
await component.locator('[data-action="composer-attach"]').click()
const shortcuts = page.locator('[role="menu"] [data-slot="menu-v2-item-shortcut"]')
await expect(shortcuts).toHaveCount(4)
const boxes = await shortcuts.evaluateAll((items) =>
items.map((item) => {
const box = item.getBoundingClientRect()
return { width: box.width, center: box.left + box.width / 2 }
}),
)
expect(new Set(boxes.map((box) => box.width)).size).toBe(1)
expect(new Set(boxes.map((box) => box.center)).size).toBe(1)
})
for (const draft of ["empty-draft", "multiline-draft", "mixed-attachments"]) {
story(`select all stays inside the composer with ${draft}`, async ({ mount, page }) => {
const component = await mount(`opencode-composer-flow--${draft}`)
@@ -1,13 +0,0 @@
import { expect, story } from "../../storybook/playwright/story"
story("keeps the comment options button pressed while its menu is open", async ({ mount, page }) => {
const component = await mount("ui-line-comment--display")
const trigger = component.locator('[data-slot="line-comment-v2-overflow"]')
const rest = await trigger.evaluate((element) => getComputedStyle(element).backgroundColor)
await trigger.click()
await expect(page.getByRole("menu")).toBeVisible()
await expect(trigger).toHaveAttribute("data-expanded", "")
await expect(trigger).not.toHaveCSS("background-color", rest)
})
+3 -5
View File
@@ -85,16 +85,14 @@ Each tab scenario reports one sample, including its raw observations. Use Playwr
The tab-switch workload uses two equally long sessions: 200 user/assistant exchanges (400 messages) per tab. Every answer includes headings, emphasis, links, a blockquote, task and nested lists, an eight-row table, and four highlighted code fences (TSX, JSON, SQL, Bash), alongside the stress fixture's reasoning and tools. The mock API deliberately returns all 400 messages in one response so every scenario measures a long loaded history, not a short paginated tail. The viewport is fixed at 1440 x 900. Results include the fixture version, Markdown and serialized-message byte counts, and message-request count. These numbers are not directly comparable to the earlier 12-exchange source / 72-exchange destination fixture.
Cold means the destination transcript has never loaded or rendered in that fresh browser context. Its measured switch includes one fixture message fetch. Warm means its complex answer was rendered and ready before switching away and back, and asserts no message fetch during the measured switch. Neither includes app startup or the source session's Markdown engine initialization. These cold results are not comparable to older prefetched cold-render results. Setup waits for mounted Markdown to finish and for the review-pane width transition to complete. Service workers are blocked to exclude the web build's background asset precache from this renderer benchmark. Screenshots are attached after measurement for the first repetition; Playwright video and trace recording are disabled for this workload, while opt-in Chrome profiling remains available. For a baseline distribution, use `--repeat-each=20 --retries=0`, keep profiling disabled, and report the median and p95 of `firstCorrectObservedMs` separately from the three-observation `stableObservedMs`.
Cold means the destination transcript has never rendered in that fresh browser context. Warm means its complex answer was rendered and ready before switching away and back. Both use the app's normal restored-tab data prefetch, which completes before measurement; neither includes app startup, the source session's Markdown engine initialization, or a cold backend fetch. The suite asserts no message fetch during either measured switch. Setup waits for mounted Markdown to finish and for the review-pane width transition to complete. Service workers are blocked to exclude the web build's background asset precache from this renderer benchmark. Screenshots are attached after measurement for the first repetition; Playwright video and trace recording are disabled for this workload, while opt-in Chrome profiling remains available. For a baseline distribution, use `--repeat-each=20 --retries=0`, keep profiling disabled, and report the median and p95 of `firstCorrectObservedMs` separately from the three-observation `stableObservedMs`.
```sh
bunx playwright test --config e2e/performance/playwright.config.ts \
timeline/session-tab-switch-benchmark.spec.ts --repeat-each=20 --retries=0
```
**The tab-switch fixture returns full history, not normal pagination.** Measure cold API navigation, Home-row opening, and restored-but-unvisited tabs separately with normal pagination. Do not combine these entry paths or compare different transports and machine-load periods as one experiment.
`inactive-tab-prefetch-benchmark.spec.ts` restores eight tabs with normal 20-message pages (44 parts and 139,257 response bytes per page). It gates heavy responses independently until every tab's attention callback has run, then measures selection with ready answer Markdown and bottom anchoring. A separate case closes an inactive tab before releasing the responses. The fixture reports speculative transcript/inbox reads, request concurrency, response bytes, and activation latency. Set `OPENCODE_PERFORMANCE_MEMORY=1` only in separate retention runs; those force GC before selection and must not be mixed into clean timing results. The scope is the production browser renderer, not total desktop memory. Live background events and eviction of previously visited transcripts are separate workloads.
**The tab-switch fixture is not an end-to-end cold-data benchmark.** It prefetches destination messages and returns full history. Measure cold API navigation, Home-row opening, and prefetched-but-unvisited tabs separately with normal pagination. Do not combine these entry paths or compare different transports and machine-load periods as one experiment.
Keep one-off reports, recorded results, and traces outside git, in the ignored `e2e/performance/results/` directory or an external artifact directory. Preserve raw observations locally and publish anonymized summaries and charts in the PR description, not as committed experiment files.
@@ -106,7 +104,7 @@ bun run bench:tabs
This runs only the tab-switch benchmark against the production build with 20 serial repetitions and no retries. It prints the median (mean of the two middle values for even sample counts) and nearest-rank p95 for `firstCorrectObservedMs` and `stableObservedMs` per scenario. Only records whose benchmark and Playwright statuses are passed and whose two metrics are finite enter the summary. Test and record statuses, missing records, and excluded samples are reported separately.
For fresh entry paths, run `bun run bench:entry` from `packages/app`. It uses the same production, serial-repetition, and reporting defaults. The cases open an empty draft from the actual Home button, create a draft with the titlebar plus from an active session, and open a cold paginated session from Home. Draft readiness requires a focused editable composer, the expected model, project control, and new tab; typing and absence of backend mutations are checked afterward. Session readiness requires the latest group, ready answer Markdown, and bottom anchoring. These cases are separate from cached tab remounts.
For fresh entry paths, run `bun run bench:entry` from `packages/app`. It uses the same production, serial-repetition, and reporting defaults. The cases open an empty draft from the actual Home button, create a draft with the titlebar plus from an active session, and open a cold paginated session from Home. Draft readiness requires a focused editable composer, the expected model, project control, and new tab; typing and absence of backend mutations are checked afterward. Session readiness requires the latest group, ready answer Markdown, and bottom anchoring. These cases are separate from prefetched tab remounts.
For milestone charts, rerun frozen builds with one workload and counterbalanced serial order. Do not connect historical medians from different transports, preparation, or machine-load periods. Show samples or ranges, name the checkpoints accurately, and distinguish experimental build snapshots from Git commits.
@@ -1,37 +0,0 @@
# Composer History Hydration
Manual benchmark for an empty destination composer. Runs the production
`ComposerEditor`, `createComposerEditor`, `createComposerHistory`, persistence
codec, and browser IndexedDB draft store. It does not run the surrounding app
shell or native desktop IPC.
Workload: 100 normal prompts with realistic review instructions/code and 100
shell commands. Separate cases have no images, 50 unique screenshots, or 50
references to 5 screenshots. The fixture generates valid 1440 x 900 PNG code
screenshots before timing and reports their exact byte sizes. Each isolated
browser context measures a cold URL-cache mount followed by a warm remount.
The database was just seeded; this does not simulate a cold disk cache.
`historyReadyMs` measures the mount action until both production history stores
are populated. This is history availability, not time to first editable input
(input can be usable before history finishes). The benchmark then verifies
ArrowUp recall and a decoded screenshot in the real editor. `recallObservedMs`
includes Playwright action/assertion overhead and is reported separately.
`mountRecallObservedMs` includes the mount, readiness checks, keyboard action,
and correct text/image completion; it also includes Playwright overhead.
IndexedDB reads and blob sizes are mechanism metrics, not desktop IPC bytes or
process memory. No timing threshold is enforced.
From `packages/app`, set `OPENCODE_HISTORY_BUILD` and
`OPENCODE_HISTORY_OUTPUT` to artifact directories outside Git, then run:
```sh
bun x vite build --config e2e/performance/composer-history/vite.config.ts
bun x playwright test --config e2e/performance/composer-history/playwright.config.ts --repeat-each=20
```
The preview server owns port 4783 and is stopped by Playwright. Preserve each
build and its revision/hash for comparisons. `BENCHMARK` JSON lines contain all
raw samples. Optional Chrome traces use the existing
`OPENCODE_PERFORMANCE_TRACE_DIR` setting; keep trace runs separate from clean
timing. Screenshots are captured after timing on the first repeat only.
@@ -1,48 +0,0 @@
import { benchmark, expect } from "../benchmark"
benchmark.use({ traceScope: "page" })
for (const shape of ["text", "unique", "repeated"]) {
benchmark(`composer global history: ${shape}, cold and warm mounts`, async ({ page, report }, testInfo) => {
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
await page.goto(`/?shape=${shape}`)
const button = page.getByRole("button", { name: "Mount empty composer", exact: true })
const input = page.getByRole("textbox", { name: "Prompt", exact: true })
const samples = []
for (const cache of ["cold", "warm"]) {
await expect(button).toBeEnabled()
const mountStarted = performance.now()
await button.click()
await expect(page.getByTestId("history-ready")).toHaveText("ready")
await expect(input).toBeEditable()
await expect(input).toBeEmpty()
const result = JSON.parse((await page.getByTestId("history-result").textContent())!)
expect(result.documents).toBe(2)
expect(result.historyReadyMs).toBeGreaterThan(0)
const start = performance.now()
await input.press("ArrowUp")
await expect(input).toContainText("Review the retry policy in src/network/request-0.ts.")
const images = page.getByRole("img", { name: "request-0.png", exact: true })
await expect(images).toHaveCount(shape === "text" ? 0 : 1)
if (shape !== "text")
await expect
.poll(() => images.evaluate((image: HTMLImageElement) => image.complete && image.naturalWidth === 1440))
.toBe(true)
samples.push({
cache,
...result,
recallObservedMs: performance.now() - start,
mountRecallObservedMs: performance.now() - mountStarted,
})
}
expect(errors).toEqual([])
report(
{ samples },
{
browser: page.context().browser()!.version(),
scope: "production composer editor/history, browser IndexedDB; no native IPC",
},
)
if (testInfo.repeatEachIndex === 0) await page.screenshot({ path: testInfo.outputPath(`${shape}.png`) })
})
}
@@ -1,174 +0,0 @@
/// <reference types="vite/client" />
import { createEffect, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { render } from "solid-js/web"
import { PlatformProvider, type Platform } from "@/runtime/platform/platform"
import { createBrowserDraftStore } from "@/runtime/persistence/drafts"
import { createComposerHistory } from "@/composer/history/store"
import { ComposerEditor } from "@/composer/editor/editor"
import { createComposerEditor } from "@/composer/editor/interaction"
import type { ComposerPersistedState } from "@/composer/types"
import "@/index.css"
const shape = new URLSearchParams(location.search).get("shape") ?? "text"
const normal = Array.from({ length: 100 }, (_, index) => {
const content =
`Review the retry policy in src/network/request-${index}.ts. Preserve cancellation and the existing error messages.\n\n` +
`The request should stop after three attempts. Add coverage for a 429 response, a connection reset, and a successful retry. Verify that only idempotent requests are retried.\n\n` +
`Report ${index}:\n\`\`\`ts\nexport async function request(input: Request) {\n const response = await fetch(input)\n if (!response.ok) throw new Error(response.statusText)\n return response.json()\n}\n\`\`\``
return {
prompt: [
{ type: "text", content, start: 0, end: content.length },
...(shape !== "text" && index % 2 === 0
? [
{
type: "image",
id: `attachment-${index}`,
filename: `request-${index}.png`,
mime: "image/png",
blob: { id: `screenshot-${shape === "repeated" ? index % 10 : index}` },
},
]
: []),
],
comments: [],
}
})
const shell = Array.from({ length: 100 }, (_, index) => {
const content = `bun test src/network/request-${index}.test.ts --timeout 30000`
return { prompt: [{ type: "text", content, start: 0, end: content.length }], comments: [] }
})
// Seed only this Playwright context, before opening the production draft store.
const request = indexedDB.open("opencode-drafts", 1)
request.onupgradeneeded = () => {
request.result.createObjectStore("documents")
request.result.createObjectStore("blobs")
}
const db = await new Promise<IDBDatabase>((resolve, reject) => {
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
const ids = [...new Set(normal.flatMap((entry) => entry.prompt.flatMap((part) => (part.blob ? [part.blob.id] : []))))]
const screenshots: { id: string; blob: Blob }[] = []
for (const id of ids) {
const canvas = document.createElement("canvas")
canvas.width = 1440
canvas.height = 900
const context = canvas.getContext("2d")!
context.fillStyle = "#15191f"
context.fillRect(0, 0, canvas.width, canvas.height)
context.font = "16px monospace"
context.fillStyle = "#b8c8d8"
context.fillText(`request.ts - ${id}`, 30, 35)
for (let line = 0; line < 38; line++) {
context.fillStyle = line % 3 ? "#a8c7ba" : "#d4a882"
context.fillText(
`${String(line + 1).padStart(3)} const response${line} = await fetch('/api/request/${id}/${line}', { signal, headers });`,
30,
70 + line * 20,
)
}
const blob = await new Promise<Blob>((resolve) => canvas.toBlob((blob) => resolve(blob!), "image/png"))
screenshots.push({ id, blob })
}
const transaction = db.transaction(["documents", "blobs"], "readwrite")
transaction.objectStore("documents").put(JSON.stringify({ entries: normal }), "opencode.global.dat:prompt-history")
transaction.objectStore("documents").put(JSON.stringify({ entries: shell }), "opencode.global.dat:prompt-history-shell")
screenshots.forEach(({ id, blob }) => transaction.objectStore("blobs").put(blob, id))
await new Promise<void>((resolve, reject) => {
transaction.oncomplete = () => resolve()
transaction.onerror = () => reject(transaction.error)
})
db.close()
const metrics = { reads: 0, blobBytes: 0, documents: 0 }
const originalGet = IDBObjectStore.prototype.get
IDBObjectStore.prototype.get = function (key) {
const request = originalGet.call(this, key)
if (this.name === "documents") metrics.documents++
if (this.name === "blobs") {
metrics.reads++
request.addEventListener("success", () => {
metrics.blobBytes += request.result?.size ?? 0
})
}
return request
}
const platform: Platform = {
platform: "web",
draftStore: createBrowserDraftStore(),
openExternal() {},
restart: async () => {},
notify: async () => {},
}
const [state, setState] = createStore({ mount: 0, ready: false, result: "" })
const workload = {
shape,
normalEntries: normal.length,
shellEntries: shell.length,
imageReferences: shape === "text" ? 0 : 50,
uniqueImages: ids.length,
storedImageBytes: screenshots.reduce((sum, item) => sum + item.blob.size, 0),
documentBytes: [normal, shell].reduce(
(sum, entries) => sum + new TextEncoder().encode(JSON.stringify({ entries })).length,
0,
),
screenshotDimensions: [1440, 900],
}
let started = 0
function mount() {
metrics.reads = 0
metrics.blobBytes = 0
metrics.documents = 0
setState({ ready: false, result: "" })
started = performance.now()
setState("mount", state.mount + 1)
}
function Destination() {
// Same history creation and editor mapping as createComposerModel. Destination draft is empty.
const history = createComposerHistory()
const store = createStore<ComposerPersistedState>({
prompt: [{ type: "text", content: "", start: 0, end: 0 }],
cursor: 0,
context: { items: [] },
})
const controller = createComposerEditor({
store,
commands: () => [],
context: () => [],
searchContextFiles: () => [],
history: {
entries: (mode) => history.entries(mode).map((entry) => ({ prompt: entry.prompt, metadata: entry.comments })),
add: (prompt, mode) => history.add(prompt, mode, []),
},
view: {
placeholder: () => "Empty destination composer",
submit: { stopping: () => false, onSubmit() {}, onStop() {} },
},
})
createEffect(() => {
if (history.entries("normal").length !== 100 || history.entries("shell").length !== 100) return
setState({
ready: true,
result: JSON.stringify({ historyReadyMs: performance.now() - started, ...metrics, ...workload }),
})
})
return <ComposerEditor controller={controller} />
}
render(
() => (
<PlatformProvider value={platform}>
<main style={{ padding: "40px", width: "900px" }}>
<h1>Composer global history: {shape}</h1>
<button onClick={mount}>Mount empty composer</button>
<output data-testid="history-ready">{state.ready ? "ready" : "idle"}</output>
<pre data-testid="history-result">{state.result}</pre>
<Show when={state.mount} keyed>
{(_mount) => <Destination />}
</Show>
</main>
</PlatformProvider>
),
document.getElementById("root")!,
)
@@ -1,11 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Composer history benchmark</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./fixture.tsx"></script>
</body>
</html>
@@ -1,20 +0,0 @@
import { defineConfig } from "@playwright/test"
import { fileURLToPath } from "node:url"
export default defineConfig({
testDir: ".",
testMatch: "composer-history.bench.ts",
workers: 1,
retries: 0,
timeout: 60_000,
reporter: "line",
outputDir: process.env.OPENCODE_HISTORY_OUTPUT,
use: { baseURL: "http://127.0.0.1:4783", viewport: { width: 1440, height: 900 }, trace: "off", video: "off" },
webServer: {
cwd: fileURLToPath(new URL("../../../", import.meta.url)),
command:
"bun x vite preview --config e2e/performance/composer-history/vite.config.ts --host 127.0.0.1 --port 4783 --strictPort",
url: "http://127.0.0.1:4783",
reuseExistingServer: false,
},
})
@@ -1,10 +0,0 @@
import { defineConfig } from "vite"
import { fileURLToPath } from "node:url"
import app from "../../../vite"
export default defineConfig({
root: fileURLToPath(new URL(".", import.meta.url)),
publicDir: fileURLToPath(new URL("../../../public", import.meta.url)),
plugins: [app],
build: { target: "esnext", outDir: process.env.OPENCODE_HISTORY_BUILD, emptyOutDir: true },
})
@@ -1,44 +0,0 @@
# Timeline Preload Lifetime
This manual benchmark uses the production app, restored session tabs, the real
`MessageTimeline` preload, and the real Markdown worker. Only API data and result
delivery timing are fixture-owned. It does not connect to a running OpenCode
service or send prompts.
From `packages/app`, set absolute `MARKDOWN_APP_BUILD_DIR` and
`MARKDOWN_RESULTS_DIR` artifact paths, then run:
```sh
bun run build
# Copy dist into MARKDOWN_APP_BUILD_DIR before editing production source.
bun --bun x playwright test --config e2e/performance/markdown/playwright.config.ts --repeat-each 20
```
Each isolated sample restores two sessions with one user message and one completed
assistant text part each. The cold target has a realistic recovery review with
either two TypeScript fences (typical) or 36 fences (large). The source has a short
completed answer. Target data is prefetched before selection, but its Markdown is
not parsed until the target is selected.
The app's service-worker generator reads `dist`, so use the normal build output
and freeze a copy, rather than overriding Vite's build output directory.
The real worker result is held after admission. The test selects the original
session again and releases the held result only after the abandoned timeline row
detaches and the selected answer reports production Markdown readiness. This
exercises both the timeline preload and the nested Markdown consumer, including
the case where either one would otherwise keep a shared parse alive.
Destination readiness and post-disposal result settlement are separate metrics.
The latter is a MessageChannel task after the result's promise microtasks drain.
The DOMParser probe counts actual DOMPurify input containing the abandoned answer
after disposal, in characters. CDP reports renderer task/script time and JS heap.
These are not worker CPU, Electron process RAM, or ungated tab-switch measurements.
In particular, this gate releases the result after destination readiness and must
not be used to claim a destination-readiness gain from skipping sanitization.
`MARKDOWN_ASSERT_DISPOSAL=1` enables the no-obsolete-sanitization assertion. Use
`MARKDOWN_RETAINED=1` only in separate post-GC runs. The repository trace collector
is available through `OPENCODE_PERFORMANCE_TRACE_DIR`, and `MARKDOWN_SCREENSHOT`
captures final output after timing. Run serially, preserve frozen builds, and keep
all results outside Git.
@@ -1,125 +0,0 @@
import type { SessionMessageInfo } from "@opencode-ai/client/promise"
import { benchmark, expect } from "../benchmark"
import { mockOpenCodeServer } from "../../utils/mock-server"
import { fixture } from "../timeline/session-timeline-stress.fixture"
import { installStressSessionTabs, installTimelineSettings, stressSessionHref } from "../timeline/timeline-test-helpers"
import { completedAnswer } from "../../../../session-ui/performance/markdown-lifetime/answer"
import { installMarkdownGate } from "./probe"
for (const size of ["typical", "large"]) {
benchmark(`timeline preload disposal: ${size}`, async ({ page, report }) => {
const answer = completedAnswer(size === "typical" ? 2 : 36)
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
const messages: Record<string, SessionMessageInfo[]> = Object.fromEntries(
[fixture.sourceID, fixture.targetID].map((id) => [
id,
[
{
id: `msg_1_${id}_user`,
type: "user",
time: { created: 1700000000000 },
text: "Review the recovery boundary.",
},
{
id: `msg_2_${id}_assistant`,
type: "assistant",
time: { created: 1700000001000, completed: 1700000008000 },
model: { id: "claude-opus-4-6", providerID: "opencode" },
agent: "build",
cost: 0.01,
tokens: { input: 100, output: 200, reasoning: 0, cache: { read: 0, write: 0 } },
finish: "stop",
content: [
{
type: "text",
text:
id === fixture.targetID
? answer
: "## Current destination\n\nThe selected session is ready.\n\n```typescript\nconst current = { ready: true }\n```",
},
],
},
] satisfies SessionMessageInfo[],
]),
)
await mockOpenCodeServer(page, {
sessions: fixture.sessions.filter((session) => session.id !== fixture.childID),
provider: fixture.provider,
directory: fixture.directory,
project: fixture.project,
pageMessages: (id) => ({ items: messages[id] ?? [] }),
})
await installTimelineSettings(page)
await installStressSessionTabs(page)
const targetPart = `msg_2_${fixture.targetID}_assistant:text:0`
const sourcePart = `msg_2_${fixture.sourceID}_assistant:text:0`
await installMarkdownGate(page, { answer, targetPart, sourcePart, href: stressSessionHref(fixture.sourceID) })
const prefetched = page.waitForResponse((response) =>
new URL(response.url()).pathname.endsWith(`/session/${fixture.targetID}/message`),
)
await page.goto(stressSessionHref(fixture.sourceID))
await prefetched
const source = page.locator(`[data-timeline-part-id="${sourcePart}"] [data-component="markdown"]`)
await expect(source).toHaveAttribute("data-markdown-ready", "")
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(fixture.targetID)}"]`).click()
await page.waitForFunction(() => Reflect.get(window, "markdownGate").held)
await expect(page.locator(`[data-timeline-part-id="${targetPart}"]`)).toBeAttached()
const cdp = await page.context().newCDPSession(page)
await cdp.send("Performance.enable")
const before = await cdp.send("Performance.getMetrics")
await page.evaluate(() => Reflect.get(window, "markdownGate").arm())
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(fixture.sourceID)}"]`).click()
await expect(source).toHaveAttribute("data-markdown-ready", "")
await expect(source.getByRole("heading", { name: "Current destination" })).toBeVisible()
await expect(page.locator(`[data-timeline-part-id="${targetPart}"]`)).toHaveCount(0)
await page.waitForFunction(() => Reflect.get(window, "markdownGate").settled > 0)
const after = await cdp.send("Performance.getMetrics")
const stats = await page.evaluate(() => {
const value = Reflect.get(window, "markdownGate")
return {
admitted: value.admitted,
responses: value.responses,
started: value.started,
ready: value.ready,
released: value.released,
settled: value.settled,
sanitizeCalls: value.sanitizeCalls,
sanitizeChars: value.sanitizeChars,
}
})
expect(stats.admitted).toBe(1)
expect(stats.responses).toBe(1)
expect(stats.ready).toBeGreaterThan(stats.started)
expect(stats.settled).toBeGreaterThan(stats.released)
expect(errors).toEqual([])
if (process.env.MARKDOWN_ASSERT_DISPOSAL === "1") expect(stats.sanitizeCalls).toBe(0)
const value = (data: typeof after, name: string) => data.metrics.find((item) => item.name === name)!.value
const retained = process.env.MARKDOWN_RETAINED === "1"
if (retained) await cdp.send("HeapProfiler.collectGarbage")
report(
{
...stats,
destinationReadyMs: stats.ready - stats.started,
releasedSettledMs: stats.settled - stats.released,
taskMs: (value(after, "TaskDuration") - value(before, "TaskDuration")) * 1000,
scriptMs: (value(after, "ScriptDuration") - value(before, "ScriptDuration")) * 1000,
usedHeapBytes: (await cdp.send("Runtime.getHeapUsage")).usedSize,
},
{
size,
retained,
answerBytes: Buffer.byteLength(answer),
messagesPerSession: 2,
partsPerAnswer: 1,
fences: size === "typical" ? 2 : 36,
browser: page.context().browser()!.version(),
transport: "playwright-route",
build: process.env.MARKDOWN_APP_BUILD_DIR,
},
)
if (process.env.MARKDOWN_SCREENSHOT)
await page.screenshot({ path: `${process.env.MARKDOWN_SCREENSHOT}/timeline-${size}.png` })
await cdp.detach()
})
}
@@ -1,24 +0,0 @@
import { defineConfig } from "@playwright/test"
import { fileURLToPath } from "node:url"
process.env.PLAYWRIGHT_PORT = "6199"
process.env.PLAYWRIGHT_SERVER_PORT = "6199"
process.env.PLAYWRIGHT_SERVER_HOST = "127.0.0.1"
export default defineConfig({
testDir: ".",
testMatch: "*.bench.ts",
outputDir: process.env.MARKDOWN_RESULTS_DIR,
workers: 1,
retries: 0,
timeout: 60_000,
expect: { timeout: 15_000 },
reporter: [["line"]],
use: { baseURL: "http://127.0.0.1:6199", viewport: { width: 1280, height: 900 }, serviceWorkers: "block" },
webServer: {
cwd: fileURLToPath(new URL("../../..", import.meta.url)),
command: `bun run serve -- --host 127.0.0.1 --port 6199 --strictPort --outDir "${process.env.MARKDOWN_APP_BUILD_DIR}"`,
url: "http://127.0.0.1:6199",
reuseExistingServer: false,
},
})
@@ -1,93 +0,0 @@
import type { Page } from "@playwright/test"
import type {
MarkdownWorkerRequest,
MarkdownWorkerResponse,
} from "../../../../session-ui/src/components/markdown-worker-protocol"
export async function installMarkdownGate(
page: Page,
input: { answer: string; sourcePart: string; targetPart: string; href: string },
) {
await page.addInitScript(({ answer, sourcePart, targetPart, href }) => {
const stats = {
admitted: 0,
responses: 0,
held: false,
started: 0,
ready: 0,
released: 0,
settled: 0,
sanitizeCalls: 0,
sanitizeChars: 0,
arm: () => {
armed = true
},
}
let armed = false
let id: number | undefined
let release: (() => void) | undefined
const descriptor = Object.getOwnPropertyDescriptor(Worker.prototype, "onmessage")!
const post = Worker.prototype.postMessage
Object.defineProperty(Worker.prototype, "onmessage", {
configurable: true,
get: descriptor.get,
set(callback: (event: MessageEvent<MarkdownWorkerResponse>) => void) {
descriptor.set!.call(this, (event: MessageEvent<MarkdownWorkerResponse>) => {
if (event.data.type === "parse" && event.data.id === id) {
stats.responses++
stats.held = true
release = () => callback.call(this, event)
return
}
callback.call(this, event)
})
},
})
Worker.prototype.postMessage = function (request: MarkdownWorkerRequest) {
if (request.type === "parse" && request.text === answer) {
id = request.id
stats.admitted++
}
post.call(this, request)
}
const parse = DOMParser.prototype.parseFromString
DOMParser.prototype.parseFromString = function (text, type) {
if (stats.released && String(text).includes("Recovery implementation review")) {
stats.sanitizeCalls++
stats.sanitizeChars += String(text).length
}
return parse.call(this, text, type)
}
document.addEventListener(
"mousedown",
(event) => {
if (!armed || stats.started) return
const target = event.target instanceof Element ? event.target.closest("a") : undefined
if (target?.getAttribute("href") !== href) return
stats.started = performance.now()
},
true,
)
// The app can retain the outgoing view until the destination is ready. Release
// only after its actual row detaches, rather than assuming click means dispose.
new MutationObserver(() => {
if (!stats.started || stats.released) return
const current = document.querySelector(`[data-timeline-part-id="${sourcePart}"] [data-markdown-ready]`)
if (!current) return
stats.ready ||= performance.now()
if (document.querySelector(`[data-timeline-part-id="${targetPart}"]`)) return
stats.released = performance.now()
performance.mark("markdown-timeline-disposed")
release!()
release = undefined
const channel = new MessageChannel()
channel.port1.onmessage = () => {
stats.settled = performance.now()
channel.port1.close()
channel.port2.close()
}
channel.port2.postMessage(null)
}).observe(document, { childList: true, subtree: true, attributes: true })
Object.defineProperty(window, "markdownGate", { value: stats })
}, input)
}
@@ -1,44 +0,0 @@
# Native Terminal Benchmark
Manual Windows benchmark. Run only in an isolated development worktree. It does
not connect to an OpenCode service, user profile, or database.
Build from `packages/app` with
`bun x vite build --config e2e/performance/terminals/vite.config.ts`, then freeze
`dist` outside the repository. Set `PLAYWRIGHT_BUILD=1`, `PLAYWRIGHT_BASE_URL` to
an unused loopback URL, `TERMINAL_BUILD` to the frozen build,
`TERMINAL_ARTIFACTS` to an existing external directory, and `TERMINAL_RESULTS`
to an external result directory. Run:
```sh
bun x playwright test --config e2e/performance/terminals/playwright.config.ts --repeat-each=20
```
The runner owns its preview server and each test owns a PowerShell ConPTY process.
Session metadata is deterministic. Native output is forwarded through Playwright's
WebSocket fixture into the real production `Terminal`, writer, Ghostty WASM/canvas,
and serializer. No output is dropped, paused, or delayed. This is native terminal
plus production renderer evidence, not the production PTY backend or Electron IPC.
The workload is 12,000 colored build/test log lines with file paths, durations, and
result descriptions. Cases separate visible output, the same output while hidden,
and closing the session tab after filling the configured scrollback. Ghostty
converts the app's 10,000-line setting to bytes at its initial 80-column width;
resizing reduces the effective row capacity. The report records actual retained
rows and the first retained fixture record rather than assuming 10,000 rows. Completion
requires the final marker in Ghostty and completion of its write callbacks, not
just WebSocket delivery. Teardown requires Home readiness and the final serialized
snapshot. Input, focus, resizing, and native process survival are checked.
`probe.ts` is included only by this benchmark build. It observes actual writes,
renderer calls, and serialization. Chrome `TaskDuration` measures renderer task
time, not total process CPU or RAM. For attribution, set
`OPENCODE_PERFORMANCE_TRACE_DIR`; keep traced runs separate from clean timing.
`TERMINAL_DRAW_PROBE=1` separately counts actual canvas draws to verify hidden
rendering; do not mix these instrumented samples with clean timing.
Use `TERMINAL_REVISION` and `TERMINAL_BUNDLE` to identify frozen artifacts.
`TERMINAL_SCREENSHOTS` captures the visible result after timing.
The benchmark has no machine-dependent performance thresholds. Keep raw logs,
snapshots, traces, and screenshots outside Git. Run heavy work through the
coordinator's exclusive gate when participating in a shared performance wave.
@@ -1,20 +0,0 @@
import { defineConfig } from "@playwright/test"
import config from "../../../playwright.config"
export default defineConfig({
...config,
testDir: ".",
testIgnore: [],
testMatch: "terminal-benchmark.spec.ts",
workers: 1,
retries: 0,
timeout: 120_000,
outputDir: process.env.TERMINAL_RESULTS,
reporter: [["line"]],
webServer: {
command: `bun x vite preview --host 127.0.0.1 --port ${new URL(process.env.PLAYWRIGHT_BASE_URL!).port} --strictPort --outDir ${process.env.TERMINAL_BUILD}`,
url: process.env.PLAYWRIGHT_BASE_URL,
reuseExistingServer: false,
},
use: { ...config.use, viewport: { width: 1440, height: 900 }, trace: "off", video: "off", serviceWorkers: "block" },
})
@@ -1,74 +0,0 @@
import { Terminal } from "ghostty-web"
import { SerializeAddon } from "../../../src/session/terminal/serialize"
export type TerminalProbe = {
term?: Terminal
writes: number
pending: number
bytes: number
renders: number
hiddenRenders: number
draws: number
hiddenDraws: number
serialized: { ms: number; bytes: number; value: string }[]
}
declare global {
interface Window {
terminalProbe: TerminalProbe
}
}
const probe: TerminalProbe = {
writes: 0,
pending: 0,
bytes: 0,
renders: 0,
hiddenRenders: 0,
draws: 0,
hiddenDraws: 0,
serialized: [],
}
window.terminalProbe = probe
const open = Terminal.prototype.open
Terminal.prototype.open = function (element) {
probe.term = this
open.call(this, element)
// Ghostty does not expose render events. This benchmark-only wrapper observes its
// actual renderer; it does not alter scheduling, parsing, or drawing.
const renderer = (this as unknown as { renderer: { render: (...args: unknown[]) => void } }).renderer
const render = renderer.render
let hidden = false
renderer.render = function (...args) {
probe.renders++
hidden = !element.checkVisibility()
if (hidden) probe.hiddenRenders++
return render.apply(this, args)
}
if (new URL(location.href).searchParams.has("terminalDrawProbe")) {
const context = element.querySelector("canvas")!.getContext("2d")!
const draw = context.drawImage
context.drawImage = function (...args: unknown[]) {
probe.draws++
if (hidden) probe.hiddenDraws++
Reflect.apply(draw, this, args)
}
}
}
const write = Terminal.prototype.write
Terminal.prototype.write = function (data, done) {
probe.writes++
probe.pending++
probe.bytes += typeof data === "string" ? new TextEncoder().encode(data).byteLength : data.byteLength
return write.call(this, data, () => {
probe.pending--
done?.()
})
}
const serialize = SerializeAddon.prototype.serialize
SerializeAddon.prototype.serialize = function (options) {
const start = performance.now()
const value = serialize.call(this, options)
probe.serialized.push({ ms: performance.now() - start, bytes: new TextEncoder().encode(value).byteLength, value })
return value
}
@@ -1,13 +0,0 @@
param([Parameter(Mandatory = $true)][string]$Fixture)
$ErrorActionPreference = 'Stop'
[Console]::WriteLine('TERMINAL_FIXTURE_READY')
while ($null -ne ($command = [Console]::ReadLine())) {
if ($command -eq 'exit') { exit 0 }
if ($command -eq 'run') {
foreach ($line in [System.IO.File]::ReadLines($Fixture)) {
[Console]::WriteLine($line)
}
[Console]::WriteLine('TERMINAL_WORKLOAD_DONE')
}
if ($command -eq 'ping') { [Console]::WriteLine('TERMINAL_PROCESS_ALIVE') }
}
@@ -1,352 +0,0 @@
import { createRequire } from "node:module"
import { mkdtemp, writeFile, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"
import { fileURLToPath } from "node:url"
import type { Page } from "@playwright/test"
import { benchmark, benchmarkDiagnostics, expect } from "../benchmark"
import { mockOpenCodeServer } from "../../utils/mock-server"
import { expectSessionTitle } from "../../utils/waits"
import type {} from "./probe"
// Use the same installed native PTY package as Core, with a fixture-owned process.
const native = createRequire(new URL("../../../../core/package.json", import.meta.url))("@lydell/node-pty") as {
spawn: (
file: string,
args: string[],
options: { cols: number; rows: number; cwd: string },
) => {
pid: number
write: (data: string) => void
resize: (cols: number, rows: number) => void
kill: () => void
onData: (handler: (data: string) => void) => { dispose: () => void }
onExit: (handler: () => void) => { dispose: () => void }
}
}
const sessionID = "ses_terminal_benchmark"
const ptyID = "pty_terminal_benchmark"
const title = "Terminal build output"
const server = process.env.PLAYWRIGHT_BASE_URL!
const href = `/server/${Buffer.from(server).toString("base64url")}/session/${sessionID}`
const lines = Array.from({ length: 12_000 }, (_, i) => {
const unit = ["session/history", "session/runner", "project/discovery", "tool/shell", "provider/stream"][i % 5]
return `\x1b[32mPASS\x1b[0m packages/core/test/${unit}-${String(i).padStart(5, "0")}.test.ts \x1b[2m[${10 + (i % 237)}ms]\x1b[0m validates ordered output and durable recovery`
}).join("\r\n")
benchmark.use({ traceScope: "interaction", viewport: { width: 1440, height: 900 } })
for (const scenario of ["visible-output", "hidden-output", "full-scrollback-teardown"] as const) {
benchmark(scenario, async ({ page, report }, info) => {
const dir = await mkdtemp(path.join(process.env.TERMINAL_ARTIFACTS ?? tmpdir(), "terminal-fixture-"))
await writeFile(path.join(dir, "build.log"), lines)
const pty = native.spawn(
"pwsh.exe",
[
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-File",
fileURLToPath(new URL("./shell.ps1", import.meta.url)),
"-Fixture",
path.join(dir, "build.log"),
],
{ cols: 120, rows: 24, cwd: dir },
)
const exited = new Promise<void>((resolve) => pty.onExit(resolve))
let output = ""
let connected = 0
let closed = 0
let send: ((data: string) => void) | undefined
const listener = pty.onData((data) => {
output += data
send?.(data)
})
const sizes: { cols: number; rows: number }[] = []
const removals: string[] = []
try {
if (process.env.TERMINAL_DRAW_PROBE) {
await page.addInitScript(() => {
const fill = CanvasRenderingContext2D.prototype.fillText
CanvasRenderingContext2D.prototype.fillText = function (...args: Parameters<typeof fill>) {
if (this.canvas instanceof HTMLCanvasElement && this.canvas.closest('[data-component="terminal"]')) {
window.terminalProbe.draws++
if (!this.canvas.checkVisibility()) window.terminalProbe.hiddenDraws++
}
Reflect.apply(fill, this, args)
}
})
}
const location = { directory: dir, project: { id: "proj_terminal_benchmark", directory: dir } }
const data = {
id: ptyID,
title: "Terminal 1",
command: "pwsh.exe",
args: [],
cwd: dir,
status: "running",
pid: pty.pid,
}
await mockOpenCodeServer(page, {
directory: dir,
project: {
id: location.project.id,
worktree: dir,
vcs: "git",
name: "terminal-benchmark",
time: { created: 1, updated: 1 },
sandboxes: [],
},
provider: {
all: [
{
id: "opencode",
name: "OpenCode",
models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } },
},
],
connected: ["opencode"],
default: { providerID: "opencode", modelID: "test" },
},
sessions: [
{
id: sessionID,
slug: sessionID,
projectID: location.project.id,
directory: dir,
title,
version: "dev",
time: { created: 1700000000000, updated: 1700000000000 },
},
],
pageMessages: () => ({ items: [] }),
})
await page.route("**/api/pty**", async (route) => {
if (route.request().method() === "DELETE") removals.push(route.request().url())
const body = route.request().postDataJSON()
if (body?.size) {
sizes.push(body.size)
pty.resize(body.size.cols, body.size.rows)
}
return route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
location,
data: route.request().url().includes("connect-token") ? { ticket: "fixture", expires_in: 60 } : data,
}),
})
})
await page.routeWebSocket(new RegExp(`/api/pty/${ptyID}/connect`), (socket) => {
connected++
send = (data) => socket.send(data)
socket.send(output.slice(Number(new URL(socket.url()).searchParams.get("cursor") ?? 0)))
socket.onMessage((data) => pty.write(String(data)))
socket.onClose(() => {
closed++
send = undefined
})
})
await page.addInitScript(
({ server, sessionID }) => {
localStorage.setItem("settings.v3", JSON.stringify({ general: { terminalPlacement: "bottom" } }))
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([{ type: "session", server, sessionId: sessionID }]),
)
},
{ server, sessionID },
)
await page.goto(
`${href}${process.env.TERMINAL_DRAW_PROBE && scenario !== "full-scrollback-teardown" ? "?terminalDrawProbe" : ""}`,
)
await expectSessionTitle(page, title)
await page.keyboard.press("Control+Backquote")
await waitForText(page, "TERMINAL_FIXTURE_READY")
const terminal = page.locator('[data-component="terminal"]')
await expect(terminal).toBeVisible()
await page.evaluate(() => document.fonts.ready.then(() => undefined))
await expect
.poll(async () => {
const size = await page.evaluate(() => ({
cols: window.terminalProbe.term!.cols,
rows: window.terminalProbe.term!.rows,
}))
return sizes.at(-1)?.cols === size.cols && sizes.at(-1)?.rows === size.rows
})
.toBe(true)
if (scenario === "hidden-output") {
await page.keyboard.press("Control+Backquote")
await expect(terminal).toBeHidden()
}
const cdp = await page.context().newCDPSession(page)
await cdp.send("Performance.enable")
const before = await cdp.send("Performance.getMetrics")
const start = await page.evaluate(() => {
window.terminalProbe.renders = 0
window.terminalProbe.hiddenRenders = 0
window.terminalProbe.draws = 0
window.terminalProbe.hiddenDraws = 0
return performance.now()
})
await benchmarkDiagnostics(page).startTrace()
// The producer is not throttled. The visible and hidden cases receive the same bytes.
pty.write("run\r")
await waitForText(page, "TERMINAL_WORKLOAD_DONE")
const produced = await page.evaluate(
(start) => ({
ms: performance.now() - start,
renders: window.terminalProbe.renders,
hiddenRenders: window.terminalProbe.hiddenRenders,
draws: window.terminalProbe.draws,
hiddenDraws: window.terminalProbe.hiddenDraws,
bytes: window.terminalProbe.bytes,
scrollback: window.terminalProbe.term!.getScrollbackLength(),
cols: window.terminalProbe.term!.cols,
rows: window.terminalProbe.term!.rows,
firstRecord: Number(
window.terminalProbe
.term!.buffer.normal.getLine(0)
?.translateToString(true)
.match(/-(\d{5})\.test\.ts/)?.[1],
),
}),
start,
)
const after = await cdp.send("Performance.getMetrics")
const cpuMs =
(after.metrics.find((x) => x.name === "TaskDuration")!.value -
before.metrics.find((x) => x.name === "TaskDuration")!.value) *
1000
let interaction: Record<string, unknown> = {}
if (scenario === "hidden-output") {
const start = await page.evaluate(() => performance.now())
await page.keyboard.press("Control+Backquote")
await expect(terminal).toBeVisible()
await waitForText(page, "TERMINAL_WORKLOAD_DONE")
interaction = { returnMs: await page.evaluate((start) => performance.now() - start, start) }
}
if (scenario === "full-scrollback-teardown") {
// Ghostty converts the configured line limit to bytes at the initial
// 80-column size. Resizing changes the effective retained row count.
expect(produced.scrollback).toBeGreaterThan(0)
expect(produced.firstRecord).toBeGreaterThan(0)
expect(produced.firstRecord).toBeLessThan(11_999)
const close = page.locator(`[data-titlebar-tab-slot]:has(a[href="${href}"]) [data-component="icon-button-v2"]`)
await expect(close).toBeVisible()
const cpuBefore = await cdp.send("Performance.getMetrics")
const start = await page.evaluate(() => performance.now())
await close.click()
await expect(page).toHaveURL("/")
await expect(page.locator('[data-component="home-session-search"]')).toBeVisible()
await expect(page.locator('[data-component="home-session-search"] input')).toBeEditable()
await expect.poll(() => page.evaluate(() => window.terminalProbe.serialized.length)).toBe(1)
interaction = await page.evaluate(
(start) => ({
homeReadyMs: performance.now() - start,
serializeMs: window.terminalProbe.serialized[0].ms,
serializedBytes: window.terminalProbe.serialized[0].bytes,
}),
start,
)
const cpuAfter = await cdp.send("Performance.getMetrics")
interaction.teardownCpuMs =
(cpuAfter.metrics.find((x) => x.name === "TaskDuration")!.value -
cpuBefore.metrics.find((x) => x.name === "TaskDuration")!.value) *
1000
const snapshot = await page.evaluate(() => window.terminalProbe.serialized[0].value)
expect(Array.from(snapshot.matchAll(/-(\d{5})\.test\.ts/g), (match) => Number(match[1]))).toEqual(
Array.from({ length: 12_000 - produced.firstRecord }, (_, index) => produced.firstRecord + index),
)
expect(snapshot).toContain("TERMINAL_WORKLOAD_DONE")
await writeFile(
path.join(
process.env.TERMINAL_ARTIFACTS ?? tmpdir(),
`${process.env.TERMINAL_BUNDLE}-${info.repeatEachIndex}.ansi`,
),
snapshot,
)
await expect(terminal).toHaveCount(0)
expect(closed).toBe(1)
// UI teardown must not terminate the native process.
pty.write("ping\r")
await expect.poll(() => output.includes("TERMINAL_PROCESS_ALIVE")).toBe(true)
}
await benchmarkDiagnostics(page).stop()
expect(connected).toBe(1)
expect(removals).toEqual([])
expect(sizes.length).toBeGreaterThan(0)
report(
{ ...produced, cpuMs, ...interaction },
{
revision: process.env.TERMINAL_REVISION,
bundle: process.env.TERMINAL_BUNDLE,
fixtureBytes: Buffer.byteLength(lines),
fixtureLines: 12_000,
transport: "Windows ConPTY -> Playwright WebSocket fixture -> production Terminal/writer/Ghostty",
scope: "Chromium renderer; not Electron total RAM or production backend IPC",
},
)
if (scenario !== "full-scrollback-teardown") {
// Validate input, focus, and resize after both visible and hidden output.
await terminal.click()
await expect(terminal.locator("textarea")).toBeFocused()
await page.keyboard.type("ping")
await page.keyboard.press("Enter")
await waitForText(page, "TERMINAL_PROCESS_ALIVE")
const columns = await page.evaluate(() => window.terminalProbe.term!.cols)
await page.setViewportSize({ width: 1100, height: 800 })
await expect.poll(() => page.evaluate(() => window.terminalProbe.term!.cols)).not.toBe(columns)
await expect
.poll(async () => sizes.at(-1)?.cols === (await page.evaluate(() => window.terminalProbe.term!.cols)))
.toBe(true)
expect(closed).toBe(0)
}
if (process.env.TERMINAL_SCREENSHOTS && scenario !== "full-scrollback-teardown") {
await page.screenshot({
path: path.join(process.env.TERMINAL_SCREENSHOTS, `${scenario}-${info.repeatEachIndex}.png`),
})
}
} finally {
listener.dispose()
try {
await benchmarkDiagnostics(page).stop()
// Stop fixture request handlers before killing their native resource. The
// app debounces PTY resize requests independently of the canvas resize.
await page.unrouteAll({ behavior: "wait" })
await page.close()
} finally {
pty.kill()
await exited
await writeFile(
path.join(
process.env.TERMINAL_ARTIFACTS ?? tmpdir(),
`${process.env.TERMINAL_BUNDLE}-${scenario}-${info.repeatEachIndex}.native.log`,
),
output,
)
await rm(dir, { recursive: true, force: true })
}
}
})
}
async function waitForText(page: Page, text: string) {
await expect
.poll(() =>
page.evaluate((text) => {
const probe = window.terminalProbe
const term = probe?.term
if (!term || probe.pending !== 0) return false
const buffer = term.buffer.active
return Array.from(
{ length: term.rows },
(_, i) => buffer.getLine(buffer.length - term.rows + i)?.translateToString(true) ?? "",
)
.join("\n")
.includes(text)
}, text),
)
.toBe(true)
}
@@ -1,17 +0,0 @@
import { mergeConfig } from "vite"
import config from "../../../vite.config"
// The probe is included only in this manual benchmark build, never in the app build.
export default mergeConfig(config, {
plugins: [
{
name: "terminal-benchmark-probe",
transformIndexHtml: {
order: "pre",
handler: () => [
{ tag: "script", attrs: { type: "module", src: "/e2e/performance/terminals/probe.ts" }, injectTo: "head" },
],
},
},
],
})
@@ -1,197 +0,0 @@
import { base64Encode } from "@opencode-ai/util/encode"
import { benchmark, benchmarkDiagnostics, expect } from "../benchmark"
import { mockOpenCodeServer } from "../../utils/mock-server"
import { expectSessionTitle } from "../../utils/waits"
import { fixture } from "./session-timeline-stress.fixture"
const sessionID = "ses_composer_write_batch"
const title = "Composer persistence workload"
const addition = " Keep the existing error handling and add coverage."
const text =
Array.from(
{ length: 180 },
(_, index) =>
`Review requirement ${index + 1}: preserve request ordering in src/queue/worker-${index % 12}.ts. ` +
`A failed request must retain its payload, report its cause, and remain safe to retry.\n` +
`Expected: await queue.flush(); expect(await repository.read(id)).toEqual(accepted);\n`,
).join("") + "Implementation notes:"
const items = Array.from({ length: 8 }, (_, index) => ({
type: "file",
path: `src/queue/worker-${index}.ts`,
selection: { startLine: 10, startChar: 0, endLine: 24, endChar: 0 },
commentID: `composer-write-batch-${index}`,
comment: `Check retry path ${index}: keep the original request identity and error cause.`,
preview: Array.from(
{ length: 24 },
(_, line) => ` const request${line} = await repository.loadPending("queue-${index}");`,
).join("\n"),
}))
const document = {
prompt: [{ type: "text", content: text, start: 0, end: text.length }],
cursor: text.length,
mode: "normal",
context: { items },
}
type Probe = { active: boolean; encodes: number; bytes: number; inputs: number; keyups: number }
type ProbeWindow = typeof window & { composerWriteBatch: Probe }
benchmark.use({
viewport: { width: 1440, height: 900 },
video: "off",
trace: "off",
serviceWorkers: "block",
traceScope: "interaction",
})
for (const scenario of ["typing", "cursor-movement", "cursor-noop", "submit-cleanup"] as const) {
benchmark(`composer-write-batch: ${scenario}`, async ({ page, report }, testInfo) => {
const submitted: Record<string, unknown>[] = []
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
provider: fixture.provider,
sessions: [{ ...fixture.sessions[0], id: sessionID, title }],
pageMessages: () => ({ items: [] }),
onPrompt: (input) => submitted.push(input.body),
})
await page.addInitScript(
({ key, value, counts }) => {
localStorage.setItem(key, JSON.stringify(value))
const probe: Probe = { active: false, encodes: 0, bytes: 0, inputs: 0, keyups: 0 }
;(window as ProbeWindow).composerWriteBatch = probe
// The draft adapter parses each schema-encoded composer document once before
// its asynchronous blob walk. Count at this boundary, not at the IDB write
// (which already discards superseded writes). This fixture is ASCII only.
if (counts) {
const parse = JSON.parse
JSON.parse = (value, reviver) => {
if (probe.active && typeof value === "string" && value.startsWith('{"prompt":[')) {
probe.encodes++
probe.bytes += value.length
}
return parse(value, reviver)
}
}
window.addEventListener("input", (event) => {
if (
probe.active &&
event.target instanceof Element &&
event.target.matches('[data-component="composer-editor"]')
)
probe.inputs++
})
window.addEventListener("keyup", (event) => {
if (
probe.active &&
event.target instanceof Element &&
event.target.matches('[data-component="composer-editor"]')
)
probe.keyups++
})
},
{
key: `${base64Encode(fixture.directory)}/prompt/${sessionID}.v2`,
value: document,
counts: process.env.OPENCODE_PERSISTENCE_COUNTS === "1",
},
)
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
await expectSessionTitle(page, title)
const editor = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(editor).toBeEditable()
await expect(editor).toHaveText(text)
await editor.focus()
await editor.press("ControlOrMeta+End")
await page.evaluate(() => window.document.fonts.ready)
const stored = async () =>
page.evaluate(async (sessionID) => {
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open("opencode-drafts", 1)
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
try {
const transaction = db.transaction("documents")
const keys = transaction.objectStore("documents").getAllKeys()
const values = transaction.objectStore("documents").getAll()
await new Promise<void>((resolve, reject) => {
transaction.oncomplete = () => resolve()
transaction.onerror = () => reject(transaction.error)
})
const index = keys.result.findIndex((key) => String(key).endsWith(`session:${sessionID}:prompt`))
// Parse after disabling the count so the observation is not part of it.
const probe = (window as ProbeWindow).composerWriteBatch
const active = probe.active
probe.active = false
const value = index < 0 ? undefined : JSON.parse(values.result[index])
probe.active = active
return value as { prompt: { content: string }[]; cursor: number; context: { items: unknown[] } } | undefined
} finally {
db.close()
}
}, sessionID)
await expect.poll(async () => (await stored())?.cursor).toBe(text.length)
expect((await stored())?.context.items).toHaveLength(items.length)
const cdp = await page.context().newCDPSession(page)
await cdp.send("Performance.enable")
await benchmarkDiagnostics(page).startTrace()
const before = await cdp.send("Performance.getMetrics")
await page.evaluate(() => {
;(window as ProbeWindow).composerWriteBatch.active = true
performance.mark("composer-write-batch-start")
})
const start = performance.now()
if (scenario === "typing") await editor.pressSequentially(addition)
if (scenario === "cursor-movement") await editor.press("ArrowLeft")
if (scenario === "cursor-noop") await editor.press("ArrowRight")
if (scenario === "submit-cleanup") await editor.press("Enter")
const expectedText = scenario === "typing" ? text + addition : scenario === "submit-cleanup" ? "" : text
const expectedCursor =
scenario === "typing"
? text.length + addition.length
: scenario === "submit-cleanup"
? 0
: text.length - Number(scenario === "cursor-movement")
await expect(editor).toHaveText(expectedText)
await expect.poll(async () => (await stored())?.cursor).toBe(expectedCursor)
const elapsedMs = performance.now() - start
const after = await cdp.send("Performance.getMetrics")
const probe = await page.evaluate(() => {
performance.mark("composer-write-batch-end")
const probe = (window as ProbeWindow).composerWriteBatch
probe.active = false
return probe
})
expect((await stored())?.prompt.map((part) => part.content).join("")).toBe(expectedText)
if (scenario === "submit-cleanup") {
await expect.poll(() => submitted.length).toBe(1)
expect(submitted[0].text).toContain(text)
expect((await stored())?.context.items).toHaveLength(0)
}
expect(probe.keyups).toBe(scenario === "typing" ? addition.length : 1)
expect(probe.inputs).toBe(scenario === "typing" ? addition.length : 0)
const metric = (name: string) =>
1000 *
((after.metrics.find((x) => x.name === name)?.value ?? 0) -
(before.metrics.find((x) => x.name === name)?.value ?? 0))
report(
{ elapsedMs, taskMs: metric("TaskDuration"), scriptMs: metric("ScriptDuration"), ...probe },
{
scenario,
promptBytes: Buffer.byteLength(text),
contextItems: items.length,
persistedBytes: Buffer.byteLength(JSON.stringify(document)),
typedCharacters: scenario === "typing" ? addition.length : 0,
counts: process.env.OPENCODE_PERSISTENCE_COUNTS === "1",
browser: page.context().browser()!.version(),
build: process.env.OPENCODE_PERSISTENCE_BUILD,
transport: "playwright-route",
completion: "editor text and committed IDB cursor",
},
)
await benchmarkDiagnostics(page).stop()
await cdp.detach()
if (testInfo.repeatEachIndex === 0) await page.screenshot({ path: testInfo.outputPath(`${scenario}.png`) })
})
}
@@ -1,166 +0,0 @@
import { benchmark, expect } from "../benchmark"
import { mockOpenCodeServer } from "../../utils/mock-server"
import { expectSessionTitle } from "../../utils/waits"
import { fixture } from "./session-timeline-stress.fixture"
import { messages } from "./session-tab-switch.fixture"
import { installStressSessionTabs, stressSessionHref } from "./timeline-test-helpers"
import { measureSessionSwitch, waitForStableTimeline } from "./session-tab-switch-probe"
const sessions = Array.from({ length: 8 }, (_, index) => ({
...fixture.sessions[0],
id: `ses_prefetch_${index}`,
title: `Renderer review ${index}`,
}))
// A normal first page, not the full-history response used by the tab-switch benchmark.
const pages = Object.fromEntries(
sessions.map((session) => [
session.id,
messages[fixture.targetID].slice(-20).map((message) => ({ ...message, id: `${message.id}_${session.id}` })),
]),
)
const workload = {
sessions: sessions.length,
messagesPerPage: 20,
payloadBytes: Object.fromEntries(
sessions.map((session) => [
session.id,
Buffer.byteLength(JSON.stringify({ data: pages[session.id].toReversed(), cursor: {} })),
]),
),
partsPerPage: pages[sessions[0].id].reduce(
(count, message) => count + (message.type === "assistant" ? message.content.length : 1),
0,
),
events: 0,
}
type ProbeWindow = Window & { __prefetchBodies?: Record<string, number> }
benchmark.use({ viewport: { width: 1440, height: 900 }, video: "off", trace: "off", serviceWorkers: "block" })
for (const close of [false, true]) {
benchmark(
`inactive tab prefetch: ${close ? "close before response" : "activate after restore"}`,
async ({ page, report }, testInfo) => {
const gate = Promise.withResolvers<void>()
const reads: string[] = []
const inboxReads: string[] = []
const pending = new Set<string>()
const concurrency = { peak: 0 }
const mutations: string[] = []
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
page.on("response", (response) => {
if (new URL(response.url()).pathname.startsWith("/api/") && !response.ok())
errors.push(`HTTP ${response.status()}: ${response.url()}`)
})
page.on("request", (request) => {
const path = new URL(request.url()).pathname
if (request.method() === "DELETE" || /\/(interrupt|prompt)$/.test(path)) mutations.push(request.url())
const inbox = path.match(/^\/api\/session\/([^/]+)\/inbox$/)
if (request.method() === "GET" && inbox) inboxReads.push(inbox[1])
})
await page.addInitScript(() => {
const host = window as ProbeWindow
host.__prefetchBodies = {}
const text = Response.prototype.text
Response.prototype.text = async function () {
const body = await text.call(this)
if (this.url) {
const path = new URL(this.url).pathname
host.__prefetchBodies![path] = (host.__prefetchBodies![path] ?? 0) + 1
}
return body
}
})
await mockOpenCodeServer(page, {
...fixture,
sessions,
pageMessages: (id) => ({ items: pages[id] ?? [] }),
beforeMessagesResponse: ({ sessionID }) => (sessionID === sessions[0].id ? Promise.resolve() : gate.promise),
onMessages: ({ sessionID, phase }) => {
if (phase === "end") return void pending.delete(sessionID)
reads.push(sessionID)
pending.add(sessionID)
concurrency.peak = Math.max(concurrency.peak, pending.size)
},
})
await installStressSessionTabs(page, { sessionIDs: sessions.map((session) => session.id) })
const cdp = await page.context().newCDPSession(page)
await cdp.send("Performance.enable")
await page.goto(stressSessionHref(sessions[0].id))
await expectSessionTitle(page, sessions[0].title)
await waitForStableTimeline(page, pages[sessions[0].id].at(-2)!.id)
// Every inactive tab's scheduled attention request must finish. This gates on the
// same production callback as prefetch, without a sleep or waiting for a removed read.
await page.waitForFunction(
(ids) => ids.every((id) => (window as ProbeWindow).__prefetchBodies![`/api/session/${id}/form`] > 0),
sessions.slice(1).map((session) => session.id),
)
const speculativeReads = reads.filter((id) => id !== sessions[0].id)
const speculativeInboxReads = inboxReads.filter((id) => id !== sessions[0].id).length
const closed = sessions.at(-1)!
if (close) {
const tab = page
.locator("[data-titlebar-tab-slot]")
.filter({ has: page.locator(`a[href="${stressSessionHref(closed.id)}"]`) })
await tab.getByRole("button", { name: "Close tab", exact: true }).click()
await expect(tab).toHaveCount(0)
}
gate.resolve()
await page.waitForFunction(
(ids) => ids.every((id) => (window as ProbeWindow).__prefetchBodies![`/api/session/${id}/message`] > 0),
reads,
)
await expectSessionTitle(page, sessions[0].title)
const heap =
process.env.OPENCODE_PERFORMANCE_MEMORY === "1"
? await cdp.send("HeapProfiler.collectGarbage").then(() => cdp.send("Runtime.getHeapUsage"))
: undefined
const task =
(await cdp.send("Performance.getMetrics")).metrics.find((metric) => metric.name === "TaskDuration")!.value *
1000
const before = reads.length
const target = sessions[1]
const result = await measureSessionSwitch(page, {
destinationIDs: pages[target.id].map((message) => message.id),
sourceIDs: pages[sessions[0].id].map((message) => message.id),
lastID: pages[target.id].at(-2)!.id,
requiredPartID: `${pages[target.id].at(-1)!.id}:text:0`,
href: stressSessionHref(target.id),
switch: async () => {
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(target.id)}"]`).click()
await expectSessionTitle(page, target.title)
},
})
await expect(
page.locator(`[data-timeline-part-id="${pages[target.id].at(-1)!.id}:text:0"] [data-component="markdown"]`),
).toHaveAttribute("data-markdown-ready", "")
expect(result.firstCorrectObservedMs).not.toBeNull()
expect(mutations).toEqual([])
expect(errors).toEqual([])
report(
{
speculativeReads: speculativeReads.length,
speculativeInboxReads,
speculativePayloadBytes: speculativeReads.reduce((bytes, id) => bytes + workload.payloadBytes[id], 0),
peakMessageRequests: concurrency.peak,
closedSessionReads: close ? reads.filter((id) => id === closed.id).length : undefined,
activationReads: reads.length - before,
startupTaskMs: task,
retainedHeap: heap,
...result,
},
{
...workload,
close,
gc: heap ? "forced retention; timing diagnostic only" : "natural; clean timing",
transport: "playwright-route",
browser: page.context().browser()!.version(),
scope: "production app renderer; not total desktop RAM",
},
)
if (testInfo.repeatEachIndex === 0) await page.screenshot({ path: testInfo.outputPath("destination.png") })
await cdp.detach()
},
)
}
@@ -48,27 +48,20 @@ benchmark("samples cached session repaint after the click", async ({ page, repor
expect(result.samples.length).toBeGreaterThan(0)
})
benchmark("loads only the selected restored tab's transcript", async ({ page, report }) => {
const loaded = new Set<string>()
benchmark("prefetches every open session tab", async ({ page, report }) => {
const prefetched = new Set<string>()
await mockStressTimeline(page, {
onMessages: (input) => {
if (!input.before && input.phase === "start") loaded.add(input.sessionID)
if (!input.before && input.phase === "start") prefetched.add(input.sessionID)
},
})
await installStressSessionTabs(page, {
sessionIDs: [fixture.sourceID, fixture.targetID, fixture.childID],
})
await installTimelineSettings(page)
const attention = Promise.all(
[fixture.targetID, fixture.childID].map((id) =>
page.waitForResponse((response) => new URL(response.url()).pathname === `/api/session/${id}/form`),
),
)
await page.goto(stressSessionHref(fixture.sourceID))
await expectSessionTitle(page, fixture.expected.sourceTitle)
await attention
await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
expect([...loaded]).toEqual([fixture.sourceID])
report({ loaded: [...loaded] })
await expect.poll(() => prefetched.has(fixture.childID)).toBe(true)
report({ prefetched: [...prefetched] })
})
@@ -49,7 +49,7 @@ scenarios.forEach((scenario) => {
expect(result.firstCorrectObservedMs).not.toBeNull()
expect(result.stableObservedMs).not.toBeNull()
expect(requests).toHaveLength(requestsBefore + (scenario.cache === "cold" ? 1 : 0))
expect(requests).toHaveLength(requestsBefore)
await expectReadyTimeline(page, fixture.targetID)
report(
{
@@ -65,7 +65,7 @@ scenarios.forEach((scenario) => {
browserVersion: page.context().browser()!.version(),
serviceWorkers: "blocked",
reviewFiles: scenario.review === "closed" ? 0 : reviewDiffs.length,
data: scenario.cache === "cold" ? "on-demand" : "cached",
data: "prefetched",
transport: process.env.OPENCODE_PERFORMANCE_HTTP_FIXTURE === "1" ? "http" : "playwright-route",
inputEvent: "mousedown",
requireReadyAnswer: true,
@@ -97,11 +97,16 @@ async function prepareSessionTabs(page: Page) {
})
await installTimelineSettings(page)
await installStressSessionTabs(page)
// Restored tabs prefetch their data even when their transcript has never rendered.
const prefetch = page.waitForResponse((response) =>
new URL(response.url()).pathname.endsWith(`/session/${fixture.targetID}/message`),
)
await page.goto(stressSessionHref(fixture.sourceID))
expect(await (await prefetch).finished()).toBeNull()
await expectSessionTitle(page, fixture.expected.sourceTitle)
await expectReadyTimeline(page, fixture.sourceID)
await expect(page.locator(`[data-timeline-part-id="${expected[fixture.targetID].answerID}"]`)).toHaveCount(0)
expect(requests).toEqual([fixture.sourceID])
expect(requests.toSorted()).toEqual([fixture.sourceID, fixture.targetID].toSorted())
return requests
}
@@ -8,8 +8,7 @@ const projectID = "proj_composer_editing"
const sessionID = "ses_composer_editing"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
test("keeps a narrow session composer contained when invoking a built-in", async ({ page }) => {
await page.setViewportSize({ width: 800, height: 600 })
test("preserves the draft when a populated command menu triggers a built-in", async ({ page }) => {
await mockOpenCodeServer(page, {
directory,
project: {
@@ -41,8 +40,6 @@ test("keeps a narrow session composer contained when invoking a built-in", async
.poll(() => input.evaluate((element) => getComputedStyle(element, "::before").content))
.toBe(`"${String.fromCodePoint(0x200b)}"`)
await expectAppVisible(composer)
await expect(page.locator('[data-slot="session-chat-panel"]')).toHaveCSS("min-width", "0px")
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await input.fill("keep me")
await composer.getByRole("button", { name: "Add images and files" }).click()
@@ -49,8 +49,8 @@ test("renames, exports, and deletes a home session from its context menu", async
await row.click({ button: "right", position: { x: 48, y: 12 } })
await expect(page).toHaveURL("/")
await expect(page.getByRole("menuitem", { name: "Rename" })).toBeVisible()
await expect(page.getByRole("menuitem", { name: "Export" })).toBeVisible()
await expect(page.getByRole("menuitem", { name: "Delete" })).toBeVisible()
await expect(page.getByRole("menuitem", { name: "Export..." })).toBeVisible()
await expect(page.getByRole("menuitem", { name: "Delete..." })).toBeVisible()
const menuBox = await page.locator('[data-component="menu-v2-content"]').boundingBox()
expect(Math.abs((menuBox?.x ?? 0) - (rowBox?.x ?? 0) - 48)).toBeLessThan(4)
@@ -88,13 +88,13 @@ test("renames, exports, and deletes a home session from its context menu", async
await renamedRow.click({ button: "right" })
const download = page.waitForEvent("download")
const exportItem = page.getByRole("menuitem", { name: "Export" })
const exportItem = page.getByRole("menuitem", { name: "Export..." })
await exportItem.click()
expect((await download).suggestedFilename()).toBe("renamed-from-home.json")
await expect(exportItem).toBeHidden()
await renamedRow.click({ button: "right" })
await page.getByRole("menuitem", { name: "Delete" }).click()
await page.getByRole("menuitem", { name: "Delete..." }).click()
const dialog = page.getByRole("dialog")
await expect(dialog).toContainText('Delete session "Renamed from Home"?')
const removed = page.waitForRequest(
@@ -1,75 +0,0 @@
import { expect, test } from "@playwright/test"
import { mockOpenCodeServer } from "../utils/mock-server"
import { expectSessionTitle } from "../utils/waits"
import { fixture } from "../performance/timeline/session-timeline-stress.fixture"
import { installStressSessionTabs, stressSessionHref } from "../performance/timeline/timeline-test-helpers"
test("inactive tabs load attention, but read transcript and inbox only on selection", async ({ page }) => {
const reads: string[] = []
const mutations: string[] = []
const errors: string[] = []
page.on("pageerror", (error) => errors.push(error.message))
page.on("response", (response) => {
if (new URL(response.url()).pathname.startsWith("/api/") && !response.ok())
errors.push(`HTTP ${response.status()}: ${response.url()}`)
})
const state = { text: "Original fixture answer" }
page.on("request", (request) => {
const path = new URL(request.url()).pathname
if (!path.startsWith("/api/")) return
if (request.method() === "GET") reads.push(path)
if (request.method() === "DELETE" || /\/(interrupt|prompt)$/.test(path)) mutations.push(path)
})
await mockOpenCodeServer(page, {
...fixture,
pageMessages: (id) => ({
items: [
{ id: `msg_${id}_user`, type: "user", text: "Review the renderer change", time: { created: 1 } },
{
id: `msg_${id}_assistant`,
type: "assistant",
agent: "build",
model: { id: "claude-opus-4-6", providerID: "opencode" },
content: [{ type: "text", text: state.text }],
time: { created: 2, completed: 3 },
},
],
}),
})
await installStressSessionTabs(page, { sessionIDs: [fixture.sourceID, fixture.targetID, fixture.childID] })
const attention = Promise.all(
[fixture.targetID, fixture.childID].flatMap((id) =>
["permission", "form"].map((kind) =>
page.waitForResponse((response) => new URL(response.url()).pathname === `/api/session/${id}/${kind}`),
),
),
)
await page.goto(stressSessionHref(fixture.sourceID))
await expectSessionTitle(page, fixture.expected.sourceTitle)
await expect(page.locator(`[data-timeline-part-id="msg_${fixture.sourceID}_assistant:text:0"]`)).toContainText(
state.text,
)
await attention
const child = page
.locator("[data-titlebar-tab-slot]")
.filter({ has: page.locator(`a[href="${stressSessionHref(fixture.childID)}"]`) })
await child.getByRole("button", { name: "Close tab", exact: true }).click()
await expect(child).toHaveCount(0)
state.text = "Latest fixture answer after tab restoration"
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(fixture.targetID)}"]`).click()
await expectSessionTitle(page, fixture.expected.targetTitle)
await expect(page.locator(`[data-timeline-part-id="msg_${fixture.targetID}_assistant:text:0"]`)).toContainText(
state.text,
)
for (const id of [fixture.sourceID, fixture.targetID]) {
expect(reads.filter((path) => path === `/api/session/${id}/message`)).toHaveLength(1)
expect(reads.filter((path) => path === `/api/session/${id}/inbox`)).toHaveLength(1)
}
expect(
reads.filter(
(path) => path === `/api/session/${fixture.childID}/message` || path === `/api/session/${fixture.childID}/inbox`,
),
).toEqual([])
expect(mutations).toEqual([])
expect(errors).toEqual([])
})
@@ -107,6 +107,8 @@ for (const position of ["top", "bottom"] as const) {
await page.getByRole("menuitem", { name: "Status", exact: true }).click()
const status = page.getByRole("dialog", { name: "Status", exact: true })
await expect(status.getByRole("tab", { name: "MCP", exact: true })).toBeVisible()
await status.getByRole("tab", { name: "Plugins", exact: true }).click()
await expect(status.getByText("opencode.json", { exact: true })).toBeVisible()
await status.getByRole("button", { name: "Close", exact: true }).click()
await expect(status).toBeHidden()
await expect(more).toBeFocused()
@@ -42,7 +42,7 @@ test("selects a base branch for a new workspace", async ({ page }) => {
await page.goto(`/new-session?draftId=${draftID}`)
await expectAppVisible(page.locator('[data-component="composer-editor"]'))
await page.getByRole("button", { name: "Local", exact: true }).click()
await page.getByRole("menuitem", { name: "New worktree", exact: true }).click()
await page.getByRole("menuitem", { name: "New workspace", exact: true }).click()
await page.getByRole("button", { name: "from main", exact: true }).click()
await page.getByRole("menuitemradio", { name: "feature/api", exact: true }).click()
@@ -9,17 +9,12 @@ const projectID = "proj_workspace_pending"
const draftID = "draft_workspace_pending"
const otherID = "ses_workspace_pending_other"
const text = "Create the workspace, then explain the pending session."
const followUp = "Then explain the setup scripts.\nInclude the install command."
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
const sessionPath = `/server/${base64Encode(server)}/session/`
const draftPath = `/new-session?draftId=${draftID}`
const headers = { "access-control-allow-origin": "*" }
test.use({
serviceWorkers: "block",
viewport: { width: 1280, height: 900 },
permissions: ["clipboard-read", "clipboard-write"],
})
test.use({ serviceWorkers: "block", viewport: { width: 1280, height: 900 } })
for (const viewport of [
{ name: "desktop", width: 1280, height: 900 },
@@ -29,44 +24,22 @@ for (const viewport of [
await page.setViewportSize(viewport)
const mock = await openDraft(page)
const pending = await submitPending(page, mock)
const editor = page.locator('[data-component="composer-editor"]')
await draftFollowUp(page)
await expect(page.locator('[data-action="composer-submit"]')).toBeDisabled()
await editor.press("Enter")
await editor.press("ControlOrMeta+Enter")
await page.locator('[data-component="composer"]').dispatchEvent("submit")
await expect(editor).toHaveText(followUp)
await expect(editor).toBeInViewport()
expect(mock.worktreeRequests).toEqual([expect.objectContaining({ from: directory })])
await expect(pending.message).toBeInViewport()
await expect(pending.shimmer).toBeInViewport()
await expect(pending.title).toBeInViewport()
const spinner = page.locator(
viewport.name === "mobile"
? '[data-slot="mobile-tabs-trigger"] [data-component="session-progress-indicator-v2"]'
: `[data-titlebar-tab-link][href="${sessionPath}${pending.sessionID}"] [data-component="session-progress-indicator-v2"]`,
)
await expect(spinner).toBeVisible()
await testInfo.attach("creating-worktree", {
body: await page.screenshot({ path: testInfo.outputPath(`pending-${viewport.name}.png`) }),
contentType: "image/png",
})
if (viewport.name === "mobile") {
await page.locator('[data-slot="mobile-tabs-trigger"]').click()
const drawer = page.locator('[data-slot="mobile-tabs-drawer"]')
const tab = drawer.locator(`[data-titlebar-tab-link][href="${sessionPath}${pending.sessionID}"]`)
await expect(tab.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
await tab.click()
await expect(drawer).toBeHidden()
await page.locator("html").evaluate((element) => {
element.setAttribute("dir", "rtl")
element.dir = "rtl"
})
await expect(page.locator("html")).toHaveAttribute("dir", "rtl")
await expect(pending.message).toBeInViewport()
await expect(pending.shimmer).toBeInViewport()
await expect(pending.title).toBeInViewport()
await expect(page.locator('[data-component="session-preparing"]')).toHaveCSS("direction", "rtl")
expect(
await page
@@ -80,14 +53,11 @@ for (const viewport of [
await expect(page).toHaveURL(`${sessionPath}${otherID}`)
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
await expect(pending.shimmer).toBeHidden()
await expect(spinner).toBeVisible()
await page.locator(`[data-titlebar-tab-link][href="${sessionPath}${pending.sessionID}"]`).click()
await expect(page).toHaveURL(pending.url)
await expect(pending.message).toHaveAttribute("data-timeline-part-id", `${pending.messageID}:text:0`)
await expect(pending.shimmer).toHaveAttribute("data-active", "true")
await expect(pending.title).toHaveText("New session")
await expect(editor).toHaveText(followUp)
expect(mock.calls).toEqual(["worktree"])
await page.locator(`[data-titlebar-tab-link][href="${sessionPath}${otherID}"]`).click()
@@ -118,133 +88,12 @@ for (const viewport of [
await expect(page).toHaveURL(pending.url)
await expect(pending.shimmer).toHaveCount(0)
await expect(spinner).toHaveCount(1)
await expect(pending.message).toHaveCount(1)
await expect(pending.message.locator('[data-slot="user-message-text"]')).toHaveText(text)
await expect(pending.message).toHaveAttribute("data-timeline-part-id", `${pending.messageID}:text:0`)
await expect(editor).toHaveText(followUp)
await expect(page.locator('[data-action="composer-submit"]')).toBeEnabled()
expect(mock.prompts).toHaveLength(1)
await page.locator('[data-action="composer-submit"]').click()
await expect
.poll(() => mock.prompts)
.toEqual([
{ sessionID: pending.sessionID, body: expect.objectContaining({ id: pending.messageID, text }) },
{ sessionID: pending.sessionID, body: expect.objectContaining({ text: followUp }) },
])
})
}
for (const direction of ["ltr", "rtl"]) {
test(`keeps the title and message stable through worktree creation in ${direction}`, async ({ page }) => {
const mock = await openDraft(page)
await page.locator("html").evaluate((element, direction) => element.setAttribute("dir", direction), direction)
const pending = await submitPending(page, mock)
await draftFollowUp(page)
await page.locator('[data-component="composer-editor"]').press("ControlOrMeta+Home")
const title = page.locator("[data-session-title]").getByRole("heading", { level: 1 })
const before = await title.boundingBox()
const messageBefore = await pending.message.boundingBox()
// Observe painted frames during the handoff, without using frame counts to wait for readiness.
const observation = await page.evaluateHandle(() => {
const frames: { title: string | null; message: boolean; spinner: boolean; draft: string | null }[] = []
let frame = 0
const sample = () => {
const title = document.querySelector<HTMLElement>("[data-session-title] h1")
const message = document.querySelector<HTMLElement>('[data-component="user-message"]')
const spinner = document.querySelector(
'[data-titlebar-tab-slot][data-active="true"] [data-component="session-progress-indicator-v2"]',
)
const editor = document.querySelector('[data-component="composer-editor"]')
frames.push({
title: title?.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }) ? title.textContent : null,
message: !!message?.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }),
spinner: !!spinner?.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }),
draft: editor?.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }) ? editor.textContent : null,
})
frame = requestAnimationFrame(sample)
}
sample()
return {
stop: () => {
cancelAnimationFrame(frame)
return frames
},
}
})
mock.worktree.resolve({ status: 200, json: { directory: workspace } })
await expect(title).toHaveText("Created workspace session")
await expect(pending.shimmer).toHaveCount(0)
await expect(pending.message.locator('[data-slot="user-message-text"]')).toHaveText(text)
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(followUp)
await expect(page.locator('[data-component="composer-editor"]')).toBeFocused()
const frames = await observation.evaluate((observation) => observation.stop())
await observation.dispose()
expect(frames.length).toBeGreaterThan(0)
expect(
frames.filter(
(frame) =>
!frame.message ||
!frame.spinner ||
frame.draft !== followUp ||
!["New session", "Created workspace session"].includes(frame.title ?? ""),
),
).toEqual([])
const after = await title.boundingBox()
const messageAfter = await pending.message.boundingBox()
expect(after?.y).toBe(before?.y)
expect(after?.height).toBe(before?.height)
expect(messageAfter).toEqual(messageBefore)
await page.locator('[data-component="composer-editor"]').pressSequentially("Also: ")
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(`Also: ${followUp}`)
expect(mock.calls).toEqual(["worktree", "session", "prompt"])
})
}
for (const failure of ["worktree", "session"]) {
test(`preserves both inputs when ${failure} creation fails`, async ({ page }) => {
const mock = await openDraft(page, { failSessionCreate: failure === "session" })
const pending = await submitPending(page, mock)
await draftFollowUp(page)
mock.worktree.resolve(
failure === "worktree"
? { status: 500, json: { message: "Worktree creation failed" } }
: { status: 200, json: { directory: workspace } },
)
await expect(page).toHaveURL(draftPath)
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(`${text}\n\n${followUp}`)
await expect(page.locator('[data-action="composer-submit"]')).toBeEnabled()
await expect(pending.shimmer).toHaveCount(0)
expect(mock.prompts).toEqual([])
})
}
test("preserves both inputs when the initial prompt cannot be sent", async ({ page }) => {
const mock = await openDraft(page)
const pending = await submitPending(page, mock)
await draftFollowUp(page)
await page.route(`**/api/session/${pending.sessionID}/prompt`, (route) =>
route.fulfill({
status: 500,
json: { message: "Prompt admission failed" },
headers,
}),
)
mock.worktree.resolve({ status: 200, json: { directory: workspace } })
await expect(pending.shimmer).toHaveCount(0)
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(`${text}\n\n${followUp}`)
await expect(page.locator('[data-action="composer-submit"]')).toBeEnabled()
await expect.poll(() => mock.calls).toEqual(["worktree", "session", "prompt", "prompt"])
expect(mock.prompts).toEqual([])
})
test("restores the original draft when worktree creation fails", async ({ page }) => {
const mock = await openDraft(page)
const pending = await submitPending(page, mock)
@@ -255,7 +104,7 @@ test("restores the original draft when worktree creation fails", async ({ page }
await expect(page.getByText("Failed to create worktree", { exact: true })).toBeVisible()
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(text)
await expect(page.locator('[data-action="composer-submit"]')).toBeEnabled()
await expect(page.getByRole("button", { name: "New worktree", exact: true })).toBeVisible()
await expect(page.getByRole("button", { name: "New workspace", exact: true })).toBeVisible()
await expect(pending.shimmer).toHaveCount(0)
await expect(pending.message).toHaveCount(0)
await expect(page.locator(`[data-titlebar-tab-link][href="${sessionPath}${pending.sessionID}"]`)).toHaveCount(0)
@@ -295,7 +144,6 @@ test("retains the draft and reuses the created workspace after session creation
test("restores the draft after closing and revisiting a pending session that fails", async ({ page }) => {
const mock = await openDraft(page)
const pending = await submitPending(page, mock)
await draftFollowUp(page)
const tab = page.locator(`[data-titlebar-tab-link][href="${sessionPath}${pending.sessionID}"]`)
await page.locator("[data-titlebar-tab-slot]").filter({ has: tab }).locator('[data-slot="tab-close"] button').click()
@@ -316,16 +164,15 @@ test("restores the draft after closing and revisiting a pending session that fai
await expect(pending.shimmer).toBeVisible()
await expect(pending.shimmer).toContainText("Creating worktree")
await expect(pending.shimmer).toHaveAttribute("data-active", "true")
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(followUp)
expect(mock.calls).toEqual(["worktree"])
mock.worktree.resolve({ status: 500, json: { message: "Worktree creation failed after revisiting the session" } })
await expect(page).toHaveURL(draftPath)
await expect(page.getByText("Failed to create worktree", { exact: true })).toBeVisible()
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(`${text}\n\n${followUp}`)
await expect(page.locator('[data-component="composer-editor"]')).toHaveText(text)
await expect(page.locator('[data-action="composer-submit"]')).toBeEnabled()
await expect(page.getByRole("button", { name: "New worktree", exact: true })).toBeVisible()
await expect(page.getByRole("button", { name: "New workspace", exact: true })).toBeVisible()
await expect(page.locator(`[data-titlebar-tab-link][href="${draftPath}"]`)).toHaveCount(1)
await expect(tab).toHaveCount(0)
await expect(pending.shimmer).toHaveCount(0)
@@ -335,17 +182,6 @@ test("restores the draft after closing and revisiting a pending session that fai
expect(mock.prompts).toEqual([])
})
async function draftFollowUp(page: Page) {
const editor = page.locator('[data-component="composer-editor"]')
await editor.pressSequentially("!")
await expect(editor).toHaveText("!")
await expect(editor).toHaveAttribute("dir", "auto")
await editor.fill("")
await page.evaluate((text) => navigator.clipboard.writeText(text), followUp)
await editor.press("ControlOrMeta+V")
await expect(editor).toHaveText(followUp)
}
async function openDraft(page: Page, options?: { failSessionCreate?: boolean }) {
const worktree = Promise.withResolvers<{ status: number; json: { directory?: string; message?: string } }>()
const calls: string[] = []
@@ -418,24 +254,6 @@ async function openDraft(page: Page, options?: { failSessionCreate?: boolean })
headers,
})
})
await page.route("**/api/agent?**", (route) =>
route.fulfill({
json: {
location: { directory: new URL(route.request().url()).searchParams.get("location[directory]") ?? directory },
data: [
{
id: "build",
name: "Build",
mode: "primary",
hidden: false,
request: { settings: {}, headers: {}, body: {} },
permissions: [],
},
],
},
headers,
}),
)
await page.addInitScript(
({ directory, draftID, otherID, server }) => {
localStorage.setItem(
@@ -458,8 +276,8 @@ async function openDraft(page: Page, options?: { failSessionCreate?: boolean })
await page.goto(draftPath)
await expectAppVisible(page.locator('[data-component="composer-editor"]'))
await page.getByRole("button", { name: "Local", exact: true }).click()
await page.getByRole("menuitem", { name: "New worktree", exact: true }).click()
await expect(page.getByRole("button", { name: "New worktree", exact: true })).toBeVisible()
await page.getByRole("menuitem", { name: "New workspace", exact: true }).click()
await expect(page.getByRole("button", { name: "New workspace", exact: true })).toBeVisible()
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
return { worktree, worktreeRequests, calls, creates, prompts }
}
@@ -474,11 +292,7 @@ async function submitPending(page: Page, mock: Awaited<ReturnType<typeof openDra
const preparing = page.locator('[data-component="session-preparing"]')
const message = page.locator('[data-component="user-message"]')
const shimmer = preparing.getByRole("status").locator('[data-component="text-shimmer"]')
const title = preparing.getByRole("heading", { level: 1 })
await expect(preparing).toBeVisible()
await expect(title).toHaveText("New session")
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
await expect(page.locator('[data-action="composer-submit"]')).toBeDisabled()
await expect(preparing.locator('[data-component="user-message"]')).toHaveCount(1)
await expect(message).toHaveCount(1)
await expect(message.locator('[data-slot="user-message-text"]')).toHaveText(text)
@@ -490,5 +304,5 @@ async function submitPending(page: Page, mock: Awaited<ReturnType<typeof openDra
await expect.poll(() => mock.calls).toEqual(["worktree"])
expect(mock.creates).toEqual([])
expect(mock.prompts).toEqual([])
return { url, sessionID, messageID, message, shimmer, title }
return { url, sessionID, messageID, message, shimmer }
}
@@ -0,0 +1,103 @@
import { expect, test } from "@playwright/test"
import { base64Encode } from "@opencode-ai/util/encode"
import { mockOpenCodeServer } from "../utils/mock-server"
const directory = "C:/Projects/extensions-demo"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
const session = {
id: "ses_project_extensions",
title: "Existing session",
directory,
projectID: "proj_extensions_demo",
time: { created: 1700000000000, updated: 1700000000000 },
}
test.use({ viewport: { width: 1440, height: 1000 }, colorScheme: "dark" })
test("project Extensions stays inside settings while plugins load", async ({ page }) => {
await mockOpenCodeServer(page, {
directory,
project: {
id: session.projectID,
canonical: directory,
name: "Extensions demo",
vcs: "git",
time: session.time,
sandboxes: [],
},
provider: { all: [], connected: [], default: {} },
sessions: [session],
pageMessages: () => ({ items: [] }),
})
await page.addInitScript(
({ server, sessionID, directory }) => {
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({ projects: { local: [{ worktree: directory, expanded: true }] } }),
)
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([{ type: "session", server, sessionId: sessionID }]),
)
},
{ server, sessionID: session.id, directory },
)
const href = `/server/${base64Encode(server)}/session/${session.id}`
await page.goto(href)
await expect(page.getByRole("heading", { name: session.title, exact: true })).toBeVisible()
await page.keyboard.press("Control+,")
const settings = page.getByTestId("settings-screen")
await settings.getByRole("tab", { name: "Projects", exact: true }).click()
await settings.getByText("Extensions demo", { exact: true }).click()
const dialog = page.getByRole("dialog")
await expect(dialog.getByRole("textbox", { name: "Name", exact: true })).toBeFocused()
const globalPlugins = Promise.withResolvers<void>()
const projectPlugins = Promise.withResolvers<void>()
await page.route(
(url) => url.pathname === "/api/plugin",
async (route) => {
const project = new URL(route.request().url()).searchParams.get("location[directory]")
await (project ? projectPlugins : globalPlugins).promise
await route.fulfill({
json: {
location: project ? { directory: project } : {},
data: (project ? ["shared-plugin", "project-plugin"] : ["shared-plugin"]).map((id) => ({
id,
source: { type: "package", target: id },
state: { status: "active" },
features: { server: true },
})),
},
})
},
)
const requested = page.waitForRequest((request) => {
const url = new URL(request.url())
return url.pathname === "/api/plugin" && url.searchParams.get("location[directory]") === directory
})
await dialog.getByRole("tab", { name: "Extensions", exact: true }).click()
await requested
await expect(page).toHaveURL(href)
await expect(dialog.getByRole("heading", { name: "Extensions", exact: true })).toBeVisible()
await expect(settings).toBeVisible()
await expect(page.getByRole("heading", { name: session.title, exact: true, includeHidden: true })).toBeHidden()
await dialog.getByRole("tab", { name: "Plugins", exact: true }).click()
await expect(dialog.getByRole("tab", { name: "Plugins", exact: true })).toHaveAttribute("aria-selected", "true")
globalPlugins.resolve()
await dialog.getByRole("tab", { name: "Scripts", exact: true }).click()
await expect(dialog.getByRole("heading", { name: "Scripts", exact: true })).toBeVisible()
await dialog.getByRole("tab", { name: "Extensions", exact: true }).click()
projectPlugins.resolve()
await dialog.getByRole("tab", { name: "Plugins", exact: true }).click()
await expect(dialog.getByText("project-plugin", { exact: true })).toBeVisible()
await dialog.getByRole("button", { name: "Shared with all projects 1", exact: true }).click()
await expect(dialog.getByText("shared-plugin", { exact: true })).toBeVisible()
await expect(page).toHaveURL(href)
await page.keyboard.press("Escape")
await expect(dialog).toBeHidden()
await expect(settings.getByRole("tab", { name: "Projects", exact: true })).toHaveAttribute("aria-selected", "true")
await expect(page.getByRole("heading", { name: session.title, exact: true, includeHidden: true })).toBeHidden()
})
@@ -6,9 +6,8 @@ const serverA = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${p
const serverB = "http://127.0.0.1:4097"
const sessionA = session("ses_server_a", "C:/server-a", "Server A session")
const sessionB = session("ses_server_b", "/home/server-b", "Server B session")
const childB = { ...session("ses_server_b_child", sessionB.directory, "Server B subagent"), parentID: sessionB.id }
test("tab busy indicator reflects activity in the tab session family", async ({ page }, info) => {
test("tab busy indicator reflects the tab server's own session status", async ({ page }) => {
await mockServers(page)
await page.addInitScript(
({ serverA, serverB, sessionA, sessionB }) => {
@@ -26,13 +25,13 @@ test("tab busy indicator reflects activity in the tab session family", async ({
const hrefA = `/server/${base64Encode(serverA)}/session/${sessionA.id}`
const hrefB = `/server/${base64Encode(serverB)}/session/${sessionB.id}`
await page.goto(hrefB)
await expect(page.getByText(sessionB.title).first()).toBeVisible()
await page.goto(hrefA)
await expect(page.getByText(sessionA.title).first()).toBeVisible()
// The parent is idle, but its tab remains active while the background child runs.
// Session B is busy on server B while server A stays the active server, so the
// busy indicator must come from the tab server's status, not the active server's.
const tabB = page.locator(`[data-titlebar-tab-slot]:has(a[href="${hrefB}"])`)
await expect(tabB.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
await tabB.screenshot({ path: info.outputPath("subagent-tab-activity.png") })
const tabA = page.locator(`[data-titlebar-tab-slot]:has(a[href="${hrefA}"])`)
await expect(tabA.locator("[data-titlebar-tab-title]")).toHaveText(sessionA.title)
@@ -61,12 +60,8 @@ async function mockServers(page: Page) {
if (url.pathname === "/api/event") return sse(route)
if (url.pathname === "/api/health") return json(route, { pid: 1 })
if (url.pathname === "/api/session/active")
return json(route, { data: url.origin === serverB ? { [childB.id]: { type: "running" } } : {} })
if (url.pathname === "/api/session")
return json(route, {
data: url.origin === serverB ? [currentSession(current), currentSession(childB)] : [currentSession(current)],
cursor: {},
})
return json(route, { data: url.origin === serverB ? { [sessionB.id]: { type: "running" } } : {} })
if (url.pathname === "/api/session") return json(route, { data: [currentSession(current)], cursor: {} })
if (url.pathname === `/api/session/${current.id}`) return json(route, { data: currentSession(current) })
if (url.pathname === `/api/session/${current.id}/message`) return json(route, { data: [], cursor: {} })
if (["/api/agent", "/api/provider", "/api/model", "/api/command", "/api/reference"].includes(url.pathname))
@@ -93,18 +93,11 @@ test("opens and searches project files inline", async ({ page }) => {
await contextButton.click()
await expect(panel.getByRole("tab", { name: "Context", selected: true })).toBeVisible()
await panel.getByRole("button", { name: "Open file" }).click()
const openFileTab = panel.getByRole("tab", { name: "Open file" })
const openFileTabClose = openFileTab.locator("..").getByRole("button", { name: "Close tab" })
await expect(openFileTab).toHaveAttribute("data-selected", "")
await expect(openFileTab.locator("use")).toHaveAttribute("href", "#opencode-v2-icon-file-tree")
await expect(openFileTab.getByText("Open file", { exact: true }).locator("..")).not.toHaveClass(/italic/)
await expect(openFileTabClose).toHaveAttribute("data-variant", "ghost-muted")
await expect(openFileTabClose).toHaveCSS("opacity", "1")
await expect(panel.getByRole("tab", { name: "Open file", selected: true })).toBeVisible()
await expect(sidebarToggle).toBeDisabled()
await expect(sidebar).toBeVisible()
await contextButton.click()
await expect(panel.getByRole("tab", { name: "Context", selected: true })).toBeVisible()
await expect(openFileTabClose).toHaveCSS("opacity", "0")
await expect(sidebar).toBeHidden()
await panel.getByRole("button", { name: "Open file" }).click()
const filter = panel.getByRole("combobox", { name: "Filter files" })
@@ -1,69 +0,0 @@
import { expect, test } from "@playwright/test"
import { fixture, pageMessages } from "../performance/timeline/session-timeline-stress.fixture"
import { installStressSessionTabs, stressSessionHref } from "../performance/timeline/timeline-test-helpers"
import { mockOpenCodeServer } from "../utils/mock-server"
for (const direction of ["ltr", "rtl"] as const) {
test(`session header groups controls and exposes server status in ${direction}`, async ({ page }) => {
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
sessions: fixture.sessions,
provider: fixture.provider,
pageMessages,
})
await installStressSessionTabs(page)
await page.addInitScript(() => {
const settings = JSON.parse(localStorage.getItem("settings.v3") ?? "{}")
localStorage.setItem(
"settings.v3",
JSON.stringify({ ...settings, general: { ...settings.general, showStatus: true } }),
)
})
await page.goto(stressSessionHref(fixture.targetID))
const header = page.locator("[data-session-title]")
const more = header.getByRole("button", { name: "More options", exact: true })
const review = header.getByRole("button", { name: "Toggle review", exact: true })
const details = header.getByRole("button", { name: "Session details", exact: true })
await expect(header.getByRole("heading")).toHaveText(fixture.expected.targetTitle)
await page.evaluate((direction) => document.documentElement.setAttribute("dir", direction), direction)
await expect(review).toBeVisible()
await expect(details).toBeVisible()
const status = page.locator('[data-slot="titlebar-v2"]').getByRole("button", { name: "Status" })
await expect(status).toBeVisible()
await expect
.poll(async () => {
const boxes = await Promise.all(
[header.getByRole("heading"), more, review, details].map((button) => button.boundingBox()),
)
const [title, menu, sidebar, summary] = boxes
if (!title || !menu || !sidebar || !summary) return false
return direction === "ltr"
? Math.abs(title.x + title.width - menu.x) <= 1 &&
menu.x + menu.width <= summary.x &&
summary.x + summary.width <= sidebar.x
: Math.abs(menu.x + menu.width - title.x) <= 1 &&
sidebar.x + sidebar.width <= summary.x &&
summary.x + summary.width <= menu.x
})
.toBe(true)
await review.click()
await expect(review).toHaveAttribute("aria-expanded", "true")
await expect(page.locator("#review-panel")).toBeVisible()
await review.click()
await expect(review).toHaveAttribute("aria-expanded", "false")
await more.click()
await expect(page.getByRole("menuitem", { name: "Server status", exact: true })).toHaveCount(0)
await page.keyboard.press("Escape")
await status.click()
const mcp = page.getByRole("tab", { name: "MCP", exact: true })
const plugins = page.getByRole("tab", { name: "Plugins", exact: true })
await expect(mcp).toHaveAttribute("aria-selected", "true")
await plugins.click()
await expect(plugins).toHaveAttribute("aria-selected", "true")
await page.keyboard.press("Escape")
await expect(mcp).toBeHidden()
})
}
@@ -0,0 +1,193 @@
import { expect, test } from "@playwright/test"
import { base64Encode } from "@opencode-ai/util/encode"
import type { OpenCodeEvent } from "@opencode-ai/client/promise"
import { fixture } from "../smoke/session-timeline.fixture"
import { mockOpenCodeServer } from "../utils/mock-server"
test("keeps history visible and recovers the composer by choosing another directory", async ({ page }) => {
const directory = "/projects/deleted"
const destination = "/projects/restored"
const sessionID = "ses_missing_location"
const session = { id: sessionID, projectID: fixture.project.id, directory, title: "Missing location" }
const events: OpenCodeEvent[] = []
const moves: unknown[] = []
await mockOpenCodeServer(page, {
directory: destination,
project: { ...fixture.project, worktree: destination },
provider: fixture.provider,
sessions: [session],
events: () => events.splice(0),
fileList: () => [],
pageMessages: () => ({
items: [{ id: "msg_saved", type: "user", text: "Keep this session history", time: { created: 1 } }],
}),
})
await page.route("**/api/**", (route) => {
if (new URL(route.request().url()).searchParams.get("location[directory]") !== directory) return route.fallback()
return route.fulfill({ status: 500, body: "", headers: { "access-control-allow-origin": "*" } })
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
await expect(page.getByText("Keep this session history", { exact: true })).toBeVisible()
await expect(page.getByRole("status")).toContainText("Session location unavailable")
await expect(page.getByRole("status")).toContainText(directory)
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toHaveCount(0)
const choose = page.getByRole("button", { name: "Choose directory", exact: true })
await expect(choose).toBeEnabled()
await choose.click()
const dialog = page.getByRole("dialog", { name: "Choose directory", exact: true })
await expect(dialog.getByRole("combobox")).toBeFocused()
await dialog.getByRole("combobox").press("Escape")
await expect(dialog).toHaveCount(0)
await expect(choose).toBeFocused()
expect(moves).toEqual([])
// The server can still reject a destination after it was selected in the picker.
await page.route(`**/api/session/${sessionID}/move`, (route) => {
if (route.request().method() !== "POST") return route.fallback()
moves.push(route.request().postDataJSON())
return route.fulfill({
status: 400,
json: { _tag: "InvalidRequestError", message: "Destination is unavailable" },
headers: { "access-control-allow-origin": "*" },
})
})
await choose.press("Enter")
await expect(dialog.getByRole("combobox")).toBeFocused()
await dialog.getByRole("combobox").fill(destination)
await dialog.getByRole("combobox").press("Enter")
await expect(dialog.locator(".directory-picker-selection")).toHaveText(destination)
await dialog.getByRole("button", { name: "Select folder", exact: true }).click()
await expect(page.getByText("Failed to move session", { exact: true })).toBeVisible()
await expect(choose).toBeEnabled()
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toHaveCount(0)
await expect(choose).toBeFocused()
expect(moves).toEqual([{ directory: destination }])
await page.route(`**/api/session/${sessionID}/move`, (route) => {
if (route.request().method() !== "POST") return route.fallback()
moves.push(route.request().postDataJSON())
session.directory = destination
events.push({
id: "evt_location_recovered",
type: "session.moved",
created: 2,
durable: { aggregateID: sessionID, seq: 1, version: 1 },
data: { sessionID, location: { directory: destination }, projectID: fixture.project.id },
})
return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } })
})
await choose.click()
await dialog.getByRole("combobox").fill(destination)
await dialog.getByRole("combobox").press("Enter")
await expect(dialog.locator(".directory-picker-selection")).toHaveText(destination)
await dialog.getByRole("button", { name: "Select folder", exact: true }).click()
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toBeEditable()
await expect(page.locator('[data-action="composer-model"]')).toBeVisible()
await expect(page.getByText("Keep this session history", { exact: true })).toBeVisible()
await expect(page).toHaveURL(new RegExp(`/session/${sessionID}$`))
expect(moves).toEqual([{ directory: destination }, { directory: destination }])
})
for (const create of [false, true]) {
test(`recovers into ${create ? "a new" : "an existing"} worktree without resolving the deleted location`, async ({
page,
}) => {
const canonical = "/projects/repository"
const directory = "/projects/deleted-worktree"
const destination = create ? "/projects/new-worktree" : "/projects/existing-worktree"
const sessionID = "ses_worktree_recovery"
const events: OpenCodeEvent[] = []
const requests: { operation: string; body: unknown }[] = []
let listing = Promise.withResolvers<void>()
let listingRequested = Promise.withResolvers<void>()
const moving = Promise.withResolvers<void>()
const moveRequested = Promise.withResolvers<void>()
await mockOpenCodeServer(page, {
directory: canonical,
project: { ...fixture.project, worktree: canonical },
provider: fixture.provider,
sessions: [{ id: sessionID, projectID: fixture.project.id, directory }],
pageMessages: () => ({
items: [{ id: "msg_worktree", type: "user", text: "Recover my worktree", time: { created: 1 } }],
}),
events: () => events.splice(0),
})
await page.route("**/api/**", async (route) => {
const url = new URL(route.request().url())
const headers = { "access-control-allow-origin": "*" }
if (url.searchParams.get("location[directory]") === directory)
return route.fulfill({ status: 500, body: "", headers })
if (url.pathname === `/api/worktree/${fixture.project.id}`) {
if (route.request().method() === "GET") {
listingRequested.resolve()
await listing.promise
return route.fulfill({
json: [
{ directory: canonical, strategy: null },
{ directory: "/projects/existing-worktree", strategy: "git" },
...Array.from({ length: 20 }, (_, index) => ({
directory: `/projects/worktree-${index}`,
strategy: "git",
})),
],
headers,
})
}
if (route.request().method() === "POST") {
requests.push({ operation: "create", body: route.request().postDataJSON() })
return route.fulfill({
json: { directory: destination, name: "new-worktree", branch: "new-worktree" },
headers,
})
}
}
if (url.pathname === `/api/session/${sessionID}/move` && route.request().method() === "POST") {
requests.push({ operation: "move", body: route.request().postDataJSON() })
moveRequested.resolve()
await moving.promise
events.push({
id: "evt_worktree_recovered",
type: "session.moved",
created: 2,
durable: { aggregateID: sessionID, seq: 1, version: 1 },
data: { sessionID, location: { directory: destination }, projectID: fixture.project.id },
})
return route.fulfill({ status: 204, headers })
}
return route.fallback()
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
await page.getByRole("button", { name: "Choose worktree", exact: true }).click()
await listingRequested.promise
await expect(page.getByRole("menuitem", { name: "Loading", exact: true })).toBeVisible()
await expect(page.getByRole("menuitem", { name: "New workspace", exact: true })).toBeVisible()
await expect(page.getByText("Recover my worktree", { exact: true })).toBeVisible()
await expect(page.getByText("Session location unavailable", { exact: true })).toBeVisible()
listing.resolve()
const existing = page.getByRole("menuitem", { name: "existing-worktree", exact: true })
await expect(existing).toBeVisible()
await expect(existing).toHaveCSS("height", "28px")
await page.keyboard.press("Escape")
await expect(page.getByRole("menu")).toHaveCount(0)
listing = Promise.withResolvers<void>()
listingRequested = Promise.withResolvers<void>()
await page.getByRole("button", { name: "Choose worktree", exact: true }).click()
await listingRequested.promise
await expect(page.getByRole("menuitem", { name: "existing-worktree", exact: true })).toBeVisible()
await expect(page.getByText("Recover my worktree", { exact: true })).toBeVisible()
listing.resolve()
await page.getByRole("menuitem", { name: create ? "New workspace" : "existing-worktree", exact: true }).click()
await moveRequested.promise
await expect(page.getByRole("button", { name: "Moving session…", exact: true })).toBeDisabled()
await expect(page.getByRole("button", { name: "Choose worktree", exact: true })).toBeDisabled()
moving.resolve()
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toBeEditable()
await expect(page.getByText("Recover my worktree", { exact: true })).toBeVisible()
expect(requests).toEqual([
...(create ? [{ operation: "create", body: { strategy: "git", from: canonical, directory: "/projects/" } }] : []),
{ operation: "move", body: { directory: destination } },
])
})
}
@@ -1,227 +0,0 @@
import { expect, test, type Page } from "@playwright/test"
import { base64Encode } from "@opencode-ai/util/encode"
import { fixture } from "../smoke/session-timeline.fixture"
import { mockOpenCodeServer } from "../utils/mock-server"
import { installSseTransport } from "../utils/sse-transport"
for (const endpoint of ["/api/location", "/api/agent"]) {
for (const recover of [false, true]) {
test(`keeps the composer when ${endpoint} ${recover ? "recovers on retry" : "fails"}`, async ({ page }) => {
const recovery = recoveryRequests(page)
const directory = "/projects/working-tree"
const sessionID = "ses_location_sync_failure"
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
provider: fixture.provider,
sessions: [{ id: sessionID, projectID: fixture.project.id, directory, title: "Workspace sync" }],
fileList: () => [],
pageMessages: () => ({
items: [{ id: "msg_saved", type: "user", text: "Keep working in this worktree", time: { created: 1 } }],
}),
})
let requests = 0
await page.route("**/api/**", (route) => {
const url = new URL(route.request().url())
if (url.pathname !== endpoint || url.searchParams.get("location[directory]") !== directory)
return route.fallback()
requests++
if (recover && requests > 1) return route.fallback()
return route.fulfill({ status: 500, body: "", headers: { "access-control-allow-origin": "*" } })
})
const failure = page.waitForResponse(
(response) => new URL(response.url()).pathname === endpoint && response.status() === 500,
)
const settled = page.waitForResponse((response) => {
const url = new URL(response.url())
return (
url.pathname === endpoint &&
url.searchParams.get("location[directory]") === directory &&
(recover ? response.ok() : requests === 3 && response.status() === 500)
)
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
await failure
await expect(page.getByText("Keep working in this worktree", { exact: true })).toBeVisible()
const prompt = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(prompt).toBeEditable()
await prompt.fill("Continue after reconnecting")
await expect(prompt).toHaveText("Continue after reconnecting")
await settled
await expect(prompt).toBeEditable()
await expect(prompt).toHaveText("Continue after reconnecting")
expect(requests).toBe(recover ? 2 : 3)
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Choose directory", exact: true })).toHaveCount(0)
expect(recovery).toEqual([])
await page.screenshot({ path: test.info().outputPath("location-sync.png") })
})
}
}
test("follows a live session move while the agent catalog is still loading", async ({ page }) => {
const recovery = recoveryRequests(page)
const directory = "/projects/old-tree"
const destination = "/projects/current-tree"
const sessionID = "ses_location_moved_while_loading"
const session = { id: sessionID, projectID: fixture.project.id, directory, title: "Moved session" }
const requested = Promise.withResolvers<void>()
const release = Promise.withResolvers<void>()
const transport = await installSseTransport(page, { server: fixture.serverKey })
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
provider: fixture.provider,
sessions: [session],
fileList: () => [],
pageMessages: () => ({
items: [{ id: "msg_saved", type: "user", text: "Follow the session move", time: { created: 1 } }],
}),
})
await page.route("**/api/**", async (route) => {
const url = new URL(route.request().url())
if (url.pathname === "/api/agent" && url.searchParams.get("location[directory]") === directory) {
requested.resolve()
await release.promise
}
return route.fallback()
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
await requested.promise
const prompt = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(prompt).toBeEditable()
await prompt.fill("Keep this draft")
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await transport.waitForConnection()
const resolved = page.waitForResponse((response) => {
const url = new URL(response.url())
return (
url.pathname === "/api/agent" && url.searchParams.get("location[directory]") === destination && response.ok()
)
})
session.directory = destination
await transport.send({
id: "evt_location_moved_while_loading",
type: "session.moved",
created: 2,
durable: { aggregateID: sessionID, seq: 1, version: 1 },
data: { sessionID, location: { directory: destination }, projectID: fixture.project.id },
})
await resolved
const delayed = page.waitForResponse((response) => {
const url = new URL(response.url())
return url.pathname === "/api/agent" && url.searchParams.get("location[directory]") === directory && response.ok()
})
release.resolve()
await delayed
await expect(prompt).toBeEditable()
await expect(prompt).toHaveText("Keep this draft")
await expect(page.getByText("Follow the session move", { exact: true })).toBeVisible()
await expect(page.locator('[data-type="location-switched"]').getByText(destination, { exact: true })).toBeVisible()
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Choose directory", exact: true })).toHaveCount(0)
expect(recovery).toEqual([])
})
test("refreshes a session moved during disconnection without losing the draft", async ({ page }) => {
const recovery = recoveryRequests(page)
const directory = "/projects/before-reconnect"
const destination = "/projects/after-reconnect"
const sessionID = "ses_location_reconnect"
const session = { id: sessionID, projectID: fixture.project.id, directory, title: "Reconnected session" }
const transport = await installSseTransport(page, { server: fixture.serverKey })
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
provider: fixture.provider,
sessions: [session],
fileList: () => [],
pageMessages: () => ({
items: [{ id: "msg_saved", type: "user", text: "Resume in the current worktree", time: { created: 1 } }],
}),
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
const prompt = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(prompt).toBeEditable()
await prompt.fill("Draft before disconnect")
const connection = await transport.waitForConnection()
const resolved = page.waitForResponse((response) => {
const url = new URL(response.url())
return (
url.pathname === "/api/location" && url.searchParams.get("location[directory]") === destination && response.ok()
)
})
session.directory = destination
await transport.close()
await transport.waitForConnection({ after: connection.id })
await resolved
await expect(prompt).toBeEditable()
await expect(prompt).toHaveText("Draft before disconnect")
await expect(page.getByText("Resume in the current worktree", { exact: true })).toBeVisible()
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Choose directory", exact: true })).toHaveCount(0)
expect(recovery).toEqual([])
})
test("ignores an old failed location read after reconnecting", async ({ page }) => {
const recovery = recoveryRequests(page)
const directory = "/projects/reconnected-tree"
const sessionID = "ses_location_stale_response"
const requested = Promise.withResolvers<void>()
const release = Promise.withResolvers<void>()
const transport = await installSseTransport(page, { server: fixture.serverKey })
await mockOpenCodeServer(page, {
directory: fixture.directory,
project: fixture.project,
provider: fixture.provider,
sessions: [{ id: sessionID, projectID: fixture.project.id, directory }],
fileList: () => [],
pageMessages: () => ({ items: [] }),
})
let requests = 0
await page.route("**/api/location?**", async (route) => {
if (new URL(route.request().url()).searchParams.get("location[directory]") !== directory) return route.fallback()
requests++
if (requests > 1) return route.fallback()
requested.resolve()
await release.promise
return route.fulfill({
status: 500,
body: "",
headers: { "access-control-allow-origin": "*" },
})
})
await page.goto(`/server/${base64Encode(fixture.serverKey)}/session/${sessionID}`)
await requested.promise
const prompt = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(prompt).toBeEditable()
await prompt.fill("Keep typing here")
const connection = await transport.waitForConnection()
const metadata = page.waitForResponse((response) => new URL(response.url()).pathname === `/api/session/${sessionID}`)
const resolved = page.waitForResponse((response) => {
const url = new URL(response.url())
return (
url.pathname === "/api/location" && url.searchParams.get("location[directory]") === directory && response.ok()
)
})
await transport.close()
await transport.waitForConnection({ after: connection.id })
await metadata
release.resolve()
await resolved
await expect(prompt).toBeEditable()
await expect(prompt).toHaveText("Keep typing here")
await expect(page.getByText("Session location unavailable", { exact: true })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Choose directory", exact: true })).toHaveCount(0)
expect(requests).toBe(2)
expect(recovery).toEqual([])
})
function recoveryRequests(page: Page) {
const requests: string[] = []
page.on("request", (request) => {
const path = new URL(request.url()).pathname
if (request.method() === "POST" && /^\/api\/(session\/[^/]+\/move$|worktree(?:\/|$))/.test(path)) requests.push(path)
})
return requests
}
@@ -308,8 +308,8 @@ for (const delivery of ["steer", "queue"] as const) {
})
const tools = page.locator('[data-timeline-part-ids="tool_queue_read,tool_queue_grep"]')
await expect(tools).toBeVisible()
await expect(tools).toHaveText(/^Used\s*1 Read, 1 Grep$/)
await expect(tools.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("1 Read, 1 Grep")
await expect(tools).toHaveText(/^Used\s*2 Read, Grep$/)
await expect(tools.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("2 Read, Grep")
await expect(thinking).toHaveCount(0)
await expect(pending).toBeVisible()
expect(mock.rows.map((row) => ({ id: row.id, delivery: row.delivery }))).toEqual([
@@ -318,7 +318,7 @@ for (const delivery of ["steer", "queue"] as const) {
await transcript.screenshot({ path: testInfo.outputPath("pending-steer.png") })
// Soft assertions let delivery run too, even when the pending ordering regresses.
await expect.soft(tools.or(pending)).toHaveText([/^Used\s*1 Read, 1 Grep$/, /U2: Also check the retry path\./])
await expect.soft(tools.or(pending)).toHaveText([/^Used\s*2 Read, Grep$/, /U2: Also check the retry path\./])
await expect
.soft(transcript.locator('[data-timeline-row="AssistantPart"]').filter({ has: tools }))
.toHaveAttribute("data-message-id", userID)
@@ -350,7 +350,7 @@ for (const delivery of ["steer", "queue"] as const) {
await expect(response).toHaveAttribute("data-message-id", inboxID)
await expect(thinking).toHaveCount(0)
await expect(tools.or(pending).or(response)).toHaveText([
/^Used\s*1 Read, 1 Grep$/,
/^Used\s*2 Read, Grep$/,
/U2: Also check the retry path\./,
/A3: Now checking the retry path for U2\./,
])
@@ -55,7 +55,7 @@ test.describe("regression: session timeline context group resize", () => {
await devtools.send("Emulation.setCPUThrottlingRate", { rate: 4 })
const context = page.locator(`[data-timeline-part-ids="${contextIDs.join(",")}"]`).first()
await expectAppVisible(context)
await expect(context.getByRole("button")).toHaveAccessibleName("Used 1 Read, 1 Glob, 1 Grep, 1 List")
await expect(context.getByRole("button")).toHaveAccessibleName("Used 4 Read, Glob, Grep, List")
const contextSelector = `[data-timeline-part-ids="${contextIDs.join(",")}"]`
const regions = defineVisualRegions({
@@ -88,7 +88,7 @@ test.describe("regression: session timeline context group resize", () => {
await page.waitForTimeout(delay)
}
await expect(context.getByRole("button")).toHaveAccessibleName("Used 1 Read, 1 Glob, 1 Grep, 1 List")
await expect(context.getByRole("button")).toHaveAccessibleName("Used 4 Read, Glob, Grep, List")
await page.waitForTimeout(700)
const trace = await stopVisualProbe<keyof typeof regions>(page)
const labels = trace.samples
@@ -107,7 +107,7 @@ test.describe("regression: session timeline context group resize", () => {
]),
)
expect(labels).toEqual(["Used 1 Read, 1 Glob, 1 Grep, 1 List"])
expect(labels).toEqual(["Used 4 Read, Glob, Grep, List"])
expect(issues, JSON.stringify(trace.samples, null, 2)).toEqual([])
})
})
@@ -55,7 +55,7 @@ test("transitions a streaming shell from writing through command execution", asy
const subtitle = tool.locator('[data-slot="basic-tool-tool-subtitle"]')
await expect(titleShimmer).toHaveAttribute("aria-label", "Shell")
await expect(titleShimmer).toHaveAttribute("data-active", "true")
await expect(subtitle).toHaveText("Writing command")
await expect(subtitle).toHaveText("Writing command...")
await expect(subtitle.locator('[data-component="text-shimmer"]')).toHaveCount(0)
await expect(tool.locator('[data-component="shell-submessage"]')).toHaveCount(0)
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveCSS("height", "28px")
@@ -75,7 +75,7 @@ test("transitions a streaming shell from writing through command execution", asy
await timeline.send(toolInputEnded({ sessionID, assistantMessageID: assistantID, id, text: input }))
await expect(titleShimmer).toHaveAttribute("data-active", "true")
await expect(subtitle).toHaveText(command)
await expect(tool).not.toContainText("Writing command")
await expect(tool).not.toContainText("Writing command...")
await timeline.send(
toolCalled({
@@ -100,7 +100,7 @@ test("shimmers and expands a running shell command", async ({ page }) => {
const tool = page.locator(`[data-timeline-part-id="${id}"]`)
await expect(tool.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
await expect(tool).not.toContainText("Writing command")
await expect(tool).not.toContainText("Writing command...")
await expect(tool.locator('[data-component="shell-submessage"]')).toHaveText(command)
await expect(tool.locator('[data-component="shell-submessage"] [data-component="text-shimmer"]')).toHaveCount(0)
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveCSS("height", "28px")
@@ -16,9 +16,9 @@ for (const locale of ["de", "ar"] as const) {
})
const group = page.locator(`[data-timeline-part-ids="${ids.join(",")}"]`)
const names = locale === "de" ? "1 Lesen, 1 Glob" : "1 \u0642\u0631\u0627\u0621\u0629, 1 Glob"
await expect(group.getByRole("button")).toHaveAccessibleName(`Used ${names}`)
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText(names)
const names = locale === "de" ? "Lesen, Glob" : "\u0642\u0631\u0627\u0621\u0629, Glob"
await expect(group.getByRole("button")).toHaveAccessibleName(`Used 2 ${names}`)
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText(`2 ${names}`)
await expect(page.locator("html")).toHaveAttribute("lang", locale)
})
}
@@ -45,10 +45,10 @@ test("expands a mixed collapsed tool stack without expanding its individual call
const group = page.locator(
'[data-timeline-part-ids="prt_stack_shell_1,prt_stack_explore,prt_stack_patch,prt_stack_shell_2"]',
)
const summary = group.getByRole("button", { name: "Used 2 Shell, 1 Agent, 1 Patch", exact: true })
const summary = group.getByRole("button", { name: "Used 4 Shell, Agent, Patch", exact: true })
await expect(summary).toHaveAttribute("aria-expanded", "false")
await expect(summary).toHaveCSS("height", "28px")
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("2 Shell, 1 Agent, 1 Patch")
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("4 Shell, Agent, Patch")
await expect(summary.locator('[data-component="tag"]')).toHaveCount(0)
await summary.click()
await expect(summary).toHaveAttribute("aria-expanded", "true")
@@ -75,8 +75,8 @@ test("leaves tools expanded by settings outside the collapsed stack", async ({ p
await expect(page.locator('[data-timeline-part-id="prt_expanded_shell"]')).toBeVisible()
const group = page.locator('[data-timeline-part-ids="prt_collapsed_patch,prt_collapsed_read"]')
await expect(group.getByRole("button", { name: "Used 1 Patch, 1 Read", exact: true })).toBeVisible()
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("1 Patch, 1 Read")
await expect(group.getByRole("button", { name: "Used 2 Patch, Read", exact: true })).toBeVisible()
await expect(group.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("2 Patch, Read")
await expect(page.locator('[data-timeline-spacing="tool"]')).toHaveCSS("padding-top", "8px")
})
@@ -114,7 +114,7 @@ test("combines follow-up patches into one three-file stack inside Used", async (
],
})
const group = page.locator('[data-component="collapsed-tool-group"]')
await group.getByRole("button", { name: "Used 1 Shell, 1 Patch", exact: true }).click()
await group.getByRole("button", { name: "Used 2 Shell, Patch", exact: true }).click()
await expect(group.getByText("2 files", { exact: true })).toBeVisible()
await timeline.send(
partUpdated(
@@ -129,7 +129,7 @@ test("combines follow-up patches into one three-file stack inside Used", async (
),
),
)
await expect(group.getByRole("button", { name: "Used 1 Shell, 2 Patch", exact: true })).toHaveAttribute(
await expect(group.getByRole("button", { name: "Used 3 Shell, Patch", exact: true })).toHaveAttribute(
"aria-expanded",
"true",
)
@@ -162,8 +162,8 @@ test("keeps failed search calls and their error cards inside the collapsed stack
await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] })
const group = page.locator('[data-timeline-part-ids="prt_error_glob,prt_error_grep"]')
const summary = group.getByRole("button", { name: "Used 1 Glob, 1 Grep", exact: true })
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("1 Glob, 1 Grep")
const summary = group.getByRole("button", { name: "Used 2 Glob, Grep", exact: true })
await expect(summary.locator('[data-slot="basic-tool-tool-title"]')).toHaveText("2 Glob, Grep")
await summary.click()
await expect(group.locator('[data-kind="tool-error-card"]')).toHaveCount(2)
const glob = group.locator('[data-timeline-part-id="prt_error_glob"]')
@@ -1,278 +0,0 @@
import { expect, test } from "@playwright/test"
import {
assistantID,
assistantMessage,
directory,
partUpdated,
reasoningPart,
sessionID,
setupTimeline,
status,
stepStarted,
toolPart,
userMessage,
} from "../performance/timeline-stability/fixture"
for (const width of [1400, 390]) {
test(`shows Working between busy and reasoning states at ${width}px`, async ({ page }, testInfo) => {
const timeline = await setupTimeline(page, {
messages: [userMessage()],
sessionStatus: { [sessionID]: { type: "busy" } },
viewport: { width, height: 900 },
})
const working = page.locator('[data-component="session-working"]')
await expect(working).toHaveCount(1)
await expect(working).toHaveRole("status")
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", "Working")
await expect(working).toBeInViewport()
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
await expect(working.locator('[data-component="text-shimmer"]')).toHaveCSS("line-height", "16px")
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await page.screenshot({ path: testInfo.outputPath(`working-${width}.png`) })
await timeline.send(stepStarted(assistantMessage([], { completed: false })))
await expect(working).toBeVisible()
const id = `prt_working_reasoning_${width}`
await timeline.send(partUpdated(reasoningPart(id, "")))
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
await expect(working).toHaveCount(0)
await timeline.send(partUpdated(reasoningPart(id, "The inspection is complete.")))
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", "Working")
await expect(working).toBeInViewport()
await timeline.send(status("idle"))
await expect(working).toHaveCount(0)
})
}
for (const name of ["shell", "patch", "subagent"] as const) {
test(`hides Working during ${name} input and execution, then restores it on completion`, async ({ page }) => {
const timeline = await setupTimeline(page, {
messages: [userMessage(), assistantMessage([], { completed: false })],
settings: { editToolPartsExpanded: true },
})
const working = page.locator('[data-component="session-working"]')
await expect(working).toBeVisible()
const id = `prt_working_${name}`
const input =
name === "shell"
? { command: "printf ready" }
: name === "patch"
? { patchText: "*** Begin Patch\n*** Add File: src/working.ts\n+export const ready = true\n*** End Patch" }
: { agent: "general", description: "Inspect working indicator", prompt: "Inspect the timeline." }
await timeline.send(partUpdated(toolPart(id, name, "streaming", input)))
const tool = page.locator(`[data-timeline-part-id="${id}"]`)
await expect(tool).toBeVisible()
await expect(working).toHaveCount(0)
const metadata =
name === "patch"
? {
files: [
{
file: "src/working.ts",
status: "added",
additions: 1,
deletions: 0,
patch: "@@ -0,0 +1 @@\n+export const ready = true",
},
],
}
: {}
await timeline.send(partUpdated(toolPart(id, name, "running", input, { metadata })))
await expect(tool).toContainText(
name === "shell" ? "printf ready" : name === "patch" ? "working.ts" : "Inspect working indicator",
)
await expect(working).toHaveCount(0)
await timeline.send(partUpdated(toolPart(id, name, "completed", input, { metadata })))
if (name === "shell") {
const group = page.locator('[data-component="collapsed-tool-group"]')
await expect(
group.getByRole("button", { name: "Used 1 Shell", exact: true, includeHidden: true }),
).toHaveAttribute("aria-expanded", "false")
await expect(group).toBeVisible()
}
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", "Working")
await expect(working).toBeVisible()
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
})
}
for (const name of ["read", "shell", "subagent"] as const) {
test(`keeps Working for grouped ${name} regardless of disclosure`, async ({ page }, testInfo) => {
await setupTimeline(page, {
viewport: { width: name === "shell" ? 390 : 1400, height: 900 },
messages: [
userMessage(),
assistantMessage(
[
toolPart("prt_grouped_previous", "read", "completed", { filePath: "package.json" }),
toolPart(
"prt_grouped_active",
name,
"running",
name === "shell"
? { command: "sleep 10" }
: name === "subagent"
? { agent: "general", description: "Inspect the timeline", prompt: "Inspect it." }
: { filePath: "src/working.ts" },
),
],
{ completed: false },
),
],
})
const working = page.locator('[data-component="session-working"]')
const group = page.locator('[data-component="collapsed-tool-group"]')
const trigger = group.locator(':scope > [data-component="collapsible"] > [data-slot="collapsible-trigger"]')
await expect(group).toHaveAttribute("data-timeline-part-ids", "prt_grouped_previous,prt_grouped_active")
await expect(trigger).toHaveAttribute("aria-expanded", "false")
await expect(working).toBeInViewport()
if (name !== "read") {
const hint = page.locator('[data-component="session-background-hint"]')
await expect(hint).toBeInViewport()
await expect(page.locator('[data-component="session-background-hint-row"]')).toHaveCSS("height", "24px")
await page.screenshot({ path: testInfo.outputPath(`working-grouped-${name}.png`) })
}
await trigger.click()
await expect(trigger).toHaveAttribute("aria-expanded", "true")
if (name === "subagent") await expect(group.getByText("Inspect the timeline", { exact: true })).toBeVisible()
if (name !== "subagent")
await expect(group.locator('[data-component="text-shimmer"][data-active="true"]')).toBeVisible()
await expect(working).toBeVisible()
await trigger.click()
await expect(trigger).toHaveAttribute("aria-expanded", "false")
await expect(working).toBeVisible()
})
}
for (const grouped of [false, true]) {
test(`uses ${grouped ? "grouped" : "standalone"} background shell presentation for Working`, async ({ page }) => {
await setupTimeline(page, {
settings: { shellToolPartsExpanded: !grouped },
messages: [
userMessage(),
assistantMessage(
[
toolPart("prt_background_previous", "shell", "completed", { command: "echo ready" }),
toolPart(
"prt_background_active",
"shell",
"completed",
{ command: "sleep 10" },
{
metadata: { shellID: "sh_working_background", status: "running" },
},
),
],
{ completed: false },
),
],
})
const working = page.locator('[data-component="session-working"]')
const group = page.locator('[data-component="collapsed-tool-group"]')
if (!grouped) {
await expect(page.locator('[data-timeline-part-id="prt_background_active"]')).toBeVisible()
await expect(working).toHaveCount(0)
return
}
const trigger = group.getByRole("button", { name: "Used 2 Shell", exact: true, includeHidden: true })
await expect(trigger).toHaveAttribute("aria-expanded", "false")
await expect(working).toBeVisible()
await trigger.click()
await expect(trigger).toHaveAttribute("aria-expanded", "true")
await expect(working).toBeVisible()
})
}
test("replaces Working with Retry and restores it on recovery", async ({ page }) => {
const assistant = assistantMessage([], { completed: false })
const timeline = await setupTimeline(page, { messages: [userMessage(), assistant] })
const working = page.locator('[data-component="session-working"]')
await expect(working).toBeVisible()
await timeline.send(status("retry"))
const retry = page.locator('[data-timeline-row="Retry"]')
await expect(retry).toContainText("Rate limited")
await expect(working).toHaveCount(0)
await timeline.send(stepStarted(assistant))
await expect(retry).toHaveCount(0)
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", "Working")
await expect(working).toBeVisible()
})
test("hides Working while assistant text streams", async ({ page }) => {
const timeline = await setupTimeline(page, {
messages: [userMessage(), assistantMessage([], { completed: false })],
})
const working = page.locator('[data-component="session-working"]')
await expect(working).toBeVisible()
await timeline.send({
id: "evt_working_text_started",
type: "session.text.started",
created: 1700000002000,
location: { directory },
durable: { aggregateID: sessionID, seq: 0, version: 1 },
data: { sessionID, assistantMessageID: assistantID, ordinal: 0 },
})
await timeline.send({
id: "evt_working_text_delta",
type: "session.text.delta",
created: 1700000002001,
location: { directory },
data: { sessionID, assistantMessageID: assistantID, ordinal: 0, delta: "The response is streaming." },
})
await expect(page.locator(`[data-timeline-part-id="${assistantID}:text:0"]`)).toContainText(
"The response is streaming.",
)
await expect(working).toHaveCount(0)
})
for (const failed of [false, true]) {
test(`shows Working before prompt admission or execution events${failed ? " after an error" : ""}`, async ({
page,
}) => {
await setupTimeline(page, {
messages: [
userMessage(),
...(failed
? [{ ...assistantMessage([]), error: { type: "provider.error", message: "Previous request failed" } }]
: []),
],
})
const working = page.locator('[data-component="session-working"]')
await expect(working).toHaveCount(0)
const release = Promise.withResolvers<void>()
await page.route(`**/api/session/${sessionID}/prompt`, async (route) => {
if (route.request().method() !== "POST") return route.fallback()
await release.promise
return route.fallback()
})
const editor = page.locator('[data-component="composer"]').getByRole("textbox")
await expect(editor).toBeEditable()
await editor.fill("Check the working indicator immediately.")
const requested = page.waitForRequest(
(request) =>
request.method() === "POST" && new URL(request.url()).pathname === `/api/session/${sessionID}/prompt`,
)
try {
await editor.press("Enter")
const request = await requested
expect(request.postDataJSON()).toMatchObject({ text: "Check the working indicator immediately." })
await expect(working).toHaveRole("status")
await expect(working.locator('[data-component="text-shimmer"]')).toHaveAttribute("aria-label", "Working")
await expect(working).toBeInViewport()
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
} finally {
release.resolve()
}
})
}
@@ -45,15 +45,15 @@ test("workspaces opens without waiting for inventory or sessions", async ({ page
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest((request) => new URL(request.url()).pathname.startsWith("/api/worktree/"))
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await settings.getByRole("tab", { name: "Workspaces", exact: true }).click()
await requested
await expect(settings.getByRole("heading", { name: "Worktrees", exact: true })).toBeVisible()
await expect(settings.getByRole("heading", { name: "Workspaces", exact: true })).toBeVisible()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
await expect(settings.getByText("No worktrees", { exact: true })).toHaveCount(0)
await expect(settings.getByText("No workspaces", { exact: true })).toHaveCount(0)
inventory.resolve()
await expect(settings.getByText(sandboxes[0], { exact: true })).toBeVisible()
await expect(settings.getByText("12 worktrees", { exact: true })).toBeVisible()
await expect(settings.getByText("12 workspaces", { exact: true })).toBeVisible()
sessions.resolve()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
@@ -63,25 +63,46 @@ test("workspaces opens without waiting for inventory or sessions", async ({ page
await route.fallback()
})
await settings.getByRole("tab", { name: "Preferences", exact: true }).click()
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await settings.getByRole("tab", { name: "Workspaces", exact: true }).click()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
refresh.resolve()
})
test("extensions opens without waiting for MCPs", async ({ page }) => {
test("extensions opens without waiting for MCPs or plugins", async ({ page }) => {
const mcps = Promise.withResolvers<void>()
const plugins = Promise.withResolvers<void>()
await page.route("**/api/mcp", async (route) => {
await mcps.promise
await route.fulfill({
json: { location: { directory }, data: [{ name: "demo-mcp", status: { status: "connected" } }] },
})
})
await page.route("**/api/plugin", async (route) => {
await plugins.promise
await route.fulfill({
json: {
location: { directory },
data: [
{
id: "demo-plugin",
source: { type: "package", target: "demo-plugin" },
state: { status: "active" },
features: { server: true },
},
],
},
})
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/mcp")
await settings.getByRole("tab", { name: "Extensions", exact: true }).click()
await requested
await expect(settings.getByRole("heading", { name: "Extensions", exact: true })).toBeVisible()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
await settings.getByRole("tab", { name: "Plugins", exact: true }).click()
await expect(settings.getByRole("tab", { name: "Plugins", exact: true })).toHaveAttribute("aria-selected", "true")
plugins.resolve()
await expect(settings.getByText("demo-plugin", { exact: true })).toBeVisible()
mcps.resolve()
await settings.getByRole("tab", { name: "MCPs", exact: true }).click()
await expect(settings.getByRole("switch", { name: "demo-mcp" })).toBeChecked()
@@ -89,7 +110,7 @@ test("extensions opens without waiting for MCPs", async ({ page }) => {
test("workspace inventory uses the settings panel scroll area", async ({ page }) => {
const settings = page.getByTestId("settings-screen")
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await settings.getByRole("tab", { name: "Workspaces", exact: true }).click()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
const list = settings.locator('[data-component="settings-list"]')
await expect(list).toHaveCSS("max-height", "none")
@@ -22,7 +22,8 @@ test("navigates to a subagent child session missing from the session list", asyn
await expectSessionTitle(page, taskDescription)
await expect(page.getByRole("heading", { name: parentTitle })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Toggle review", exact: true })).toBeVisible()
const titlebarRight = page.locator("#opencode-titlebar-right")
await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1)
})
test("returns to the parent session with Escape", async ({ page }) => {
@@ -9,32 +9,6 @@ const sessionB = session("ses_tab_b", "Tab B session")
const sessionC = session("ses_tab_c", "Tab C session")
const unresolvedSessionID = "ses_tab_unresolved"
test("new session tab hugs its content", async ({ page }) => {
await mockServer(page)
await page.addInitScript(
({ server, sessionID, directory }) => {
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([
{ type: "session", server, sessionId: sessionID },
{ type: "draft", server, directory, draftID: "draft_tab_width" },
]),
)
},
{ server, sessionID: sessionA.id, directory: sessionA.directory },
)
const href = `/server/${base64Encode(server)}/session/${sessionA.id}`
await page.goto(href)
const sessionTab = page.locator(`[data-titlebar-tab-slot]:has(a[href="${href}"])`)
const draftTab = page.locator('[data-titlebar-tab-slot]:has(a[href^="/new-session?draftId="])')
await expect(draftTab).toContainText("New session")
const width = (await draftTab.boundingBox())?.width ?? 0
expect(width).toBeGreaterThan(100)
expect(width).toBeLessThan((await sessionTab.boundingBox())?.width ?? 0)
})
test("pressing mouse down on a tab navigates before mouse up", async ({ page }) => {
await mockServer(page)
await page.addInitScript(
@@ -147,10 +121,7 @@ test("vertical tabs show project details, resize, and navigate", async ({ page }
await mockServer(page)
await page.addInitScript(
({ server, sessionA, sessionB }) => {
localStorage.setItem(
"settings.v3",
JSON.stringify({ appearance: { tabLayout: "vertical" }, general: { showStatus: true } }),
)
localStorage.setItem("settings.v3", JSON.stringify({ appearance: { tabLayout: "vertical" } }))
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([
@@ -173,26 +144,7 @@ test("vertical tabs show project details, resize, and navigate", async ({ page }
await expect(tabA).toContainText(sessionA.title)
await expect(tabB).toContainText(sessionB.title)
await expect(tabB.locator('[data-slot="tab-project"]')).toHaveText("tab-project")
await expect(sidebar.getByRole("button", { name: "Home", exact: true })).toHaveText("Home")
await expect(sidebar.getByRole("button", { name: "New session" })).toBeVisible()
await expect(sidebar.locator('[data-slot="vertical-tabs-footer"]')).toBeVisible()
const status = sidebar.getByRole("button", { name: "Status", exact: true })
await expect(status).toBeVisible()
await expect
.poll(async () => {
const bounds = await sidebar.boundingBox()
const button = await status.boundingBox()
return !!bounds && !!button && bounds.x + bounds.width - button.x - button.width <= 12
})
.toBe(true)
await expect(page.locator('[data-slot="titlebar-v2"]')).toBeHidden()
await expect
.poll(async () => {
const button = await sidebar.getByRole("button", { name: "New session" }).boundingBox()
const tab = await tabA.boundingBox()
return !!button && !!tab && button.y + button.height < tab.y
})
.toBe(true)
await expect(page.locator('[data-slot="titlebar-tabs"]')).toHaveCount(0)
const handle = sidebar.locator('[data-component="resize-handle"]')
@@ -134,8 +134,8 @@ for (const theme of ["light", "dark"] as const) {
await expect(view.send).toBeDisabled()
await expectBackground(view.send, "contrast")
await page.getByRole("button", { name: "Local", exact: true }).click()
await page.getByRole("menuitem", { name: "New worktree", exact: true }).click()
await expect(page.getByRole("button", { name: "New worktree", exact: true })).toBeVisible()
await page.getByRole("menuitem", { name: "New workspace", exact: true }).click()
await expect(page.getByRole("button", { name: "New workspace", exact: true })).toBeVisible()
await view.input.fill("Inspect this fixture workspace.")
await expect(view.send).toBeEnabled()
await expectBackground(view.send, "contrast")
@@ -11,6 +11,12 @@ beforeAll(async () => {
useLocation: () => ({}),
useSearchParams: () => [{}, () => undefined],
}))
mock.module("@opencode-ai/ui/context", () => ({
createSimpleContext: () => ({
use: () => undefined,
provider: () => undefined,
}),
}))
const mod = await import("./comments")
createCommentSessionForTest = mod.createCommentSessionForTest
})
+17 -5
View File
@@ -10,9 +10,14 @@ import { createScopedCache } from "@/runtime/server/scoped-cache"
import { uuid } from "@/runtime/persistence/uuid"
import type { SelectedLineRange } from "@/workspaces/files/model"
import { useWorkspaceLocation } from "@/workspaces/location"
import { CommentStore, type LineComment } from "./schema"
export type { LineComment } from "./schema"
export type LineComment = {
id: string
file: string
selection: SelectedLineRange
comment: string
time: number
}
type CommentFocus = { file: string; id: string }
@@ -32,6 +37,10 @@ function decodeSessionKey(key: string) {
}
}
type CommentStore = {
comments: Record<string, LineComment[]>
}
function aggregate(comments: Record<string, LineComment[]>) {
return Object.keys(comments)
.flatMap((file) => comments[file] ?? [])
@@ -170,9 +179,12 @@ export function createCommentSessionForTest(comments: Record<string, LineComment
}
function createCommentSession(scope: ServerScope, dir: string, id: string | undefined) {
const [store, setStore, _, ready] = persisted(Persist.serverScoped(scope, dir, id, "comments"), CommentStore, {
comments: {},
})
const [store, setStore, _, ready] = persisted(
Persist.serverScoped(scope, dir, id, "comments"),
createStore<CommentStore>({
comments: {},
}),
)
const session = createCommentSessionState(store, setStore)
return {
+22 -26
View File
@@ -1,4 +1,4 @@
import { batch, untrack, type Accessor } from "solid-js"
import { batch, type Accessor } from "solid-js"
import type { SetStoreFunction, Store } from "solid-js/store"
import type {
ComposerAgentPart,
@@ -23,47 +23,43 @@ export function createComposerEditorActions(input: ComposerStateStoreInput) {
return typeof value === "function" ? value() : value
}
const setStore = () => tuple()[1]
const clearRetry = () => {
if (untrack(() => store().retry) !== undefined) setStore()("retry", undefined)
}
const clearRetry = () => setStore()("retry", undefined)
return {
get state() {
return store()
},
setPrompt(prompt: ComposerPrompt, cursor?: number) {
// Persisted setters encode on every call, even inside a reactive batch.
batch(() => setStore()({ prompt, ...(cursor !== undefined ? { cursor } : {}), retry: undefined }))
batch(() => {
setStore()("prompt", prompt)
if (cursor !== undefined) setStore()("cursor", cursor)
clearRetry()
})
},
setCursor(cursor: number) {
if (untrack(() => store().cursor) === cursor) return
setStore()("cursor", cursor)
},
setMode(mode: "normal" | "shell") {
if (untrack(() => store().mode === mode && store().retry === undefined)) return
setStore()({ mode, retry: undefined })
setStore()("mode", mode)
clearRetry()
},
setText(content: string) {
batch(() =>
setStore()((state) => ({
prompt: [
{ type: "text", content, start: 0, end: content.length },
...state.prompt.filter((part) => part.type === "image"),
],
cursor: content.length,
retry: undefined,
})),
)
batch(() => {
setStore()("prompt", (prompt) => [
{ type: "text", content, start: 0, end: content.length },
...prompt.filter((part) => part.type === "image"),
])
setStore()("cursor", content.length)
clearRetry()
})
},
addText(content: string) {
const cursor = store().cursor ?? promptLength(store().prompt)
batch(() =>
setStore()((state) => ({
prompt: insertText(state.prompt, cursor, content),
cursor: cursor + content.length,
retry: undefined,
})),
)
batch(() => {
setStore()("prompt", (prompt) => insertText(prompt, cursor, content))
setStore()("cursor", cursor + content.length)
clearRetry()
})
},
removeContext(key: string) {
setStore()("context", "items", (items) => items.filter((item) => item.key !== key))
+4 -7
View File
@@ -91,7 +91,7 @@ export function ComposerEditor(props: ComposerEditorProps) {
event.currentTarget.value = ""
}}
/>
<Show when={!view.draftOnly && state.popover.type !== "closed"}>
<Show when={state.popover.type !== "closed"}>
<ComposerEditorPopover
emptyLabel={i18n.t("ui.promptInput.noMatchingItems")}
items={props.controller.suggestions()}
@@ -185,7 +185,7 @@ export function ComposerEditor(props: ComposerEditorProps) {
props.controller.onInput(prompt.map((part) => part.content).join(""), [...prompt, ...images], cursor)
}}
onKeyDown={(event) => {
if (!view.draftOnly && props.controller.onKeyDown(event)) return
if (props.controller.onKeyDown(event)) return
const mod = event.metaKey || event.ctrlKey
if (mod && event.key === "ArrowUp" && !event.shiftKey && !event.altKey) {
if (view.submit.queue?.editFirst()) event.preventDefault()
@@ -238,7 +238,7 @@ export function ComposerEditor(props: ComposerEditorProps) {
style={buttons()}
>
<ComposerEditorAddMenu
disabled={view.draftOnly || state.mode === "shell"}
disabled={state.mode === "shell"}
title={i18n.t("ui.promptInput.add")}
keybind={props.attachKeybind ?? ["Mod", "U"]}
attachLabel={i18n.t("ui.promptInput.attachments")}
@@ -555,10 +555,7 @@ export function ComposerEditorAddMenu(props: {
aria-label={props.title}
/>
<Menu.Portal>
<Menu.Content
class="[&_[data-slot=menu-v2-item-shortcut]]:w-8 [&_[data-slot=menu-v2-item-shortcut]]:justify-center"
style={{ "min-width": "180px" }}
>
<Menu.Content style={{ "min-width": "180px" }}>
<Menu.Item onSelect={props.onAttach} shortcut={props.attachShortcut}>
{props.attachLabel}
</Menu.Item>
@@ -29,7 +29,6 @@ export type ComposerSelectControl = {
}
export type ComposerEditorView = {
draftOnly?: boolean
placeholder?: Accessor<string>
add?: {
onAttach: () => void
@@ -333,7 +332,6 @@ export function createComposerEditor(input: {
draft.removeAttachment(id)
},
canSubmit() {
if (input.view.draftOnly) return false
const persisted = draft.state
if (state.mode === "shell") {
return persisted.prompt.some((part) => "content" in part && !!part.content.trim())
@@ -349,7 +347,6 @@ export function createComposerEditor(input: {
restoreFocus,
onInput(value: string, prompt?: ComposerPersistedState["prompt"], cursor?: number) {
if (prompt) draft.setPrompt(prompt, cursor)
if (input.view.draftOnly) return
dispatch({ type: "input.changed", value, persist: !prompt })
},
onCursor(cursor: number) {
@@ -365,7 +362,6 @@ export function createComposerEditor(input: {
dispatch({ type: "mode.shell" })
},
submit(options?: { alternate?: boolean }) {
if (input.view.draftOnly) return
input.view.submit.onSubmit(options)
dispatch({ type: "popover.close" })
},
@@ -1,9 +1,7 @@
import { describe, expect, test } from "bun:test"
import type { Prompt } from "@/composer/state"
import { prependHistoryEntry, type PromptHistoryComment } from "./entry"
import { Schema } from "effect"
import { PromptHistoryState } from "../schema"
import { Persistence } from "@/runtime/persistence/schema"
import { upgradeHistoryState } from "./store"
const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
@@ -60,11 +58,7 @@ describe("Composer history", () => {
})
test("upgrades stored prompt arrays once at the persistence boundary", () => {
expect(
Schema.decodeUnknownSync(Persistence.withInitial(PromptHistoryState, { entries: [] }))({
entries: [text("stored")],
}),
).toEqual({
expect(upgradeHistoryState({ entries: [text("stored")] })).toEqual({
entries: [{ prompt: text("stored"), comments: [] }],
})
})
+15 -3
View File
@@ -1,12 +1,24 @@
import type { Prompt } from "@/composer/state"
import type { SelectedLineRange } from "@/workspaces/files/model"
import { clonePrompt } from "../prompt-parts"
import type { PromptHistoryComment, PromptHistoryEntry } from "../schema"
export type { PromptHistoryComment, PromptHistoryEntry } from "../schema"
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 = PromptHistoryEntry
function cloneSelection(selection: SelectedLineRange): SelectedLineRange {
+21 -9
View File
@@ -1,4 +1,4 @@
import { type SetStoreFunction, type Store } from "solid-js/store"
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
import type { Prompt } from "@/composer/state"
import { Persist, persisted } from "@/runtime/persistence/storage"
import {
@@ -8,14 +8,28 @@ import {
type PromptHistoryStoredEntry,
} from "./entry"
import { clonePrompt } from "../prompt-parts"
import { PromptHistoryState } from "../schema"
export type ComposerHistoryStore = {
entries: (mode: "normal" | "shell") => PromptHistoryStoredEntry[]
add: (prompt: Prompt, mode: "normal" | "shell", comments: PromptHistoryComment[]) => void
}
type PromptHistoryState = typeof PromptHistoryState.Type
type PromptHistoryState = { entries: PromptHistoryStoredEntry[] }
export function upgradeHistoryState(value: unknown) {
if (!value || typeof value !== "object" || Array.isArray(value) || !("entries" in value)) return value
const entries = value.entries
if (!Array.isArray(entries)) return value
return {
...value,
entries: entries.flatMap((entry): PromptHistoryStoredEntry[] => {
if (Array.isArray(entry)) return [{ prompt: clonePrompt(entry as Prompt), comments: [] }]
if (!entry || typeof entry !== "object" || !("prompt" in entry) || !Array.isArray(entry.prompt)) return []
if (!("comments" in entry) || !Array.isArray(entry.comments)) return []
return [entry as PromptHistoryStoredEntry]
}),
}
}
function createComposerHistoryStore(
normal: Store<PromptHistoryState>,
@@ -37,14 +51,12 @@ function createComposerHistoryStore(
export function createComposerHistory() {
const [normal, setNormal, normalInit] = persisted(
Persist.prompt(Persist.global("prompt-history")),
PromptHistoryState,
{ entries: [] },
{ ...Persist.prompt(Persist.global("prompt-history")), migrate: upgradeHistoryState },
createStore<PromptHistoryState>({ entries: [] }),
)
const [shell, setShell, shellInit] = persisted(
Persist.prompt(Persist.global("prompt-history-shell")),
PromptHistoryState,
{ entries: [] },
{ ...Persist.prompt(Persist.global("prompt-history-shell")), migrate: upgradeHistoryState },
createStore<PromptHistoryState>({ entries: [] }),
)
const history = createComposerHistoryStore(normal, setNormal, shell, setShell)
return {
-12
View File
@@ -9,15 +9,3 @@ export function clonePrompt(prompt: Prompt): Prompt {
export function promptLength(prompt: Prompt) {
return prompt.reduce((length, part) => length + ("content" in part ? part.content.length : 0), 0)
}
export function appendPrompt(prompt: Prompt, following: Prompt): Prompt {
const start = promptLength(prompt)
const offset = start + 2
return [
...clonePrompt(prompt),
{ type: "text", content: "\n\n", start, end: offset },
...clonePrompt(following).map((part) =>
part.type === "image" ? part : { ...part, start: part.start + offset, end: part.end + offset },
),
]
}
-224
View File
@@ -1,224 +0,0 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { Persistence } from "@/runtime/persistence/schema"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import {
CommentStore,
ComposerStore,
DEFAULT_PROMPT,
PromptHistoryState,
type TextPart,
type ImageAttachmentPart,
type FileAttachmentPart,
type LineComment,
} from "./schema"
const text: TextPart = { type: "text", content: "hello", start: 0, end: 5 }
const image: Omit<ImageAttachmentPart, "blob"> = {
type: "image",
id: "image",
filename: "image.png",
mime: "image/png",
}
const comment = { id: "comment", path: "src/app.ts", selection: { start: 1, end: 2 }, comment: "note", time: 1 }
describe("composer persistence schemas", () => {
test("defaults missing or invalid fields independently and normalizes the cursor", () => {
const decode = Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)
expect(decode({})).toEqual({ prompt: DEFAULT_PROMPT, context: { items: [] } })
const value = decode({
prompt: [null, { type: "unknown" }],
cursor: -1,
mode: "unknown",
model: { providerID: 42, modelID: "model" },
retry: { id: "bad", agent: "build", providerID: "provider", modelID: "model" },
context: { items: [{ type: "file", path: "src/app.ts", commentID: "note", key: "stale" }, null] },
})
expect(value.prompt).toEqual(DEFAULT_PROMPT)
expect(value.cursor).toBe(0)
expect(value.mode).toBeUndefined()
expect(value.model).toBeUndefined()
expect(value.retry).toBeUndefined()
expect(value.context.items).toEqual([
{ type: "file", path: "src/app.ts", commentID: "note", key: "file:src/app.ts:undefined:undefined:c=note" },
])
expect(decode({ prompt: false, cursor: Infinity, context: null })).toEqual({
prompt: DEFAULT_PROMPT,
context: { items: [] },
})
const first = decode({})
first.prompt[0] = { type: "text", content: "changed", start: 0, end: 7 }
expect(decode({}).prompt).toEqual(DEFAULT_PROMPT)
})
test("drops invalid parts without losing valid mentions or optional field recovery", () => {
const value = Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)({
prompt: [
text,
{ type: "agent", content: "@build", start: 5, end: 11, name: "build" },
{ type: "skill", content: "@effect", start: 11, end: 18, id: "effect", name: "Effect" },
{ type: "agent", content: "@broken", start: 18, end: 25, name: 42 },
{
type: "file",
path: "src/app.ts",
content: "@src/app.ts",
start: 18,
end: 29,
selection: { startLine: "broken" },
mime: 42,
filename: "app.ts",
source: { type: "invalid" },
},
],
model: { providerID: "provider", modelID: "model", variant: null },
retry: { id: "msg_retry", agent: "build", providerID: "provider", modelID: "model", variant: false },
})
expect(value.prompt.map((part) => part.type)).toEqual(["text", "agent", "skill", "file"])
expect(value.prompt[3]).toEqual({
type: "file",
path: "src/app.ts",
content: "@src/app.ts",
start: 18,
end: 29,
filename: "app.ts",
})
expect(value.model?.variant).toBeNull()
expect(value.retry).toEqual({
id: SessionMessage.ID.make("msg_retry"),
agent: "build",
providerID: "provider",
modelID: "model",
})
expect(
Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)(Schema.encodeSync(ComposerStore)(value)),
).toEqual(value)
})
test("preserves file source variants through canonical round trips", () => {
const sourceText = { value: "@source", start: 0, end: 7 }
const sources: NonNullable<FileAttachmentPart["source"]>[] = [
{ type: "file", path: "src/app.ts", text: sourceText },
{ type: "resource", clientName: "docs", uri: "docs://example", text: sourceText },
{
type: "symbol",
path: "src/app.ts",
name: "App",
kind: 1,
range: { start: { line: 1, character: 0 }, end: { line: 2, character: 1 } },
text: sourceText,
},
]
const value = Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)({
prompt: sources.map((source) => ({
type: "file",
path: "src/app.ts",
content: "@source",
start: 0,
end: 7,
source,
selection: { startLine: 1, startChar: 0, endLine: 2, endChar: 1 },
})),
})
expect(value.prompt).toHaveLength(3)
expect(value.prompt.map((part) => part.type === "file" && part.source)).toEqual(sources)
expect(
Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)(Schema.encodeSync(ComposerStore)(value)),
).toEqual(value)
})
test("migrates inline images but never encodes dataUrl or unresolved references", () => {
const value = Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)({
prompt: [
{ ...image, dataUrl: "data:image/png;base64,YQ==", sourcePath: "/image.png" },
{ ...image, blob: { id: "data:image/png;base64,Yg==" } },
{ ...image, blob: { id: "hash", url: "blob:hydrated" } },
{ ...image, blob: { id: "missing" } },
{ ...image, blob: { id: "bad", url: "https://example.com/image.png" } },
{ ...image, blob: { id: "missing" }, dataUrl: "data:image/png;base64,YQ==" },
],
})
expect(value.prompt).toHaveLength(3)
expect(value.prompt[0]).toEqual({
...image,
sourcePath: "/image.png",
blob: { id: "data:image/png;base64,YQ==", url: "data:image/png;base64,YQ==" },
})
const encoded = Schema.encodeSync(ComposerStore)(value)
expect(JSON.stringify(encoded)).not.toContain("dataUrl")
expect(
Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)(encoded),
).toEqual(value)
})
test("migrates legacy history arrays and recovers entries, parts, and comments independently", () => {
const value = Schema.decodeUnknownSync(Persistence.withInitial(PromptHistoryState, { entries: [] }))({
entries: [
[text, null, { ...image, dataUrl: "data:image/png;base64,YQ==" }],
null,
{},
{ prompt: false, comments: [] },
{ prompt: [text, { type: "invalid" }], comments: [comment, { ...comment, selection: null }, false] },
{ prompt: [text], comments: false },
],
})
expect(value.entries).toHaveLength(3)
expect(value.entries[0].prompt).toHaveLength(2)
expect(value.entries[0].comments).toEqual([])
expect(value.entries[1]).toEqual({ prompt: [text], comments: [comment] })
expect(value.entries[2]).toEqual({ prompt: [text], comments: [] })
const encoded = Schema.encodeSync(PromptHistoryState)(value)
expect(encoded.entries?.every((entry) => !Array.isArray(entry))).toBe(true)
expect(JSON.stringify(encoded)).not.toContain("dataUrl")
expect(Schema.decodeUnknownSync(Persistence.withInitial(PromptHistoryState, { entries: [] }))(encoded)).toEqual(
value,
)
expect(
Schema.decodeUnknownSync(Persistence.withInitial(PromptHistoryState, { entries: [] }))({ entries: "invalid" }),
).toEqual({ entries: [] })
})
test("recovers comments per file and entry without discarding healthy siblings", () => {
const line: LineComment = {
id: "comment",
file: "src/app.ts",
selection: { start: 1, end: 2, side: "additions", endSide: "deletions" },
comment: "note",
time: 1,
}
const value = Schema.decodeUnknownSync(Persistence.withInitial(CommentStore, { comments: {} }))({
comments: {
"src/app.ts": [line, null, { ...line, time: "bad" }, { ...line, selection: { start: 2, end: 4, side: "bad" } }],
"broken.ts": { invalid: true },
"healthy.ts": [{ ...line, file: "healthy.ts" }],
},
})
expect(value.comments["src/app.ts"]).toEqual([line, { ...line, selection: { start: 2, end: 4 } }])
expect(value.comments["broken.ts"]).toEqual([])
expect(value.comments["healthy.ts"]).toEqual([{ ...line, file: "healthy.ts" }])
expect(
Schema.decodeUnknownSync(Persistence.withInitial(CommentStore, { comments: {} }))(
Schema.encodeSync(CommentStore)(value),
),
).toEqual(value)
expect(Schema.decodeUnknownSync(Persistence.withInitial(CommentStore, { comments: {} }))({})).toEqual({
comments: {},
})
expect(Schema.decodeUnknownSync(Persistence.withInitial(CommentStore, { comments: {} }))({ comments: [] })).toEqual(
{ comments: {} },
)
})
})
-212
View File
@@ -1,212 +0,0 @@
import { Schema, SchemaGetter } from "effect"
import { checksum } from "@opencode-ai/util/encode"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Skill } from "@opencode-ai/schema/skill"
import { Persistence } from "@/runtime/persistence/schema"
import { FileSelection, SelectedLineRange } from "@/workspaces/files/types"
const PartBase = {
content: Schema.String,
start: Schema.Number,
end: Schema.Number,
}
const SourceText = Schema.Struct({ value: Schema.String, start: Schema.Number, end: Schema.Number })
const Position = Schema.Struct({ line: Schema.Number, character: Schema.Number })
const FilePartSource = Schema.Union([
Schema.Struct({ type: Schema.Literal("file"), text: SourceText, path: Schema.String }),
Schema.Struct({
type: Schema.Literal("symbol"),
text: SourceText,
path: Schema.String,
range: Schema.Struct({ start: Position, end: Position }),
name: Schema.String,
kind: Schema.Number,
}),
Schema.Struct({ type: Schema.Literal("resource"), text: SourceText, clientName: Schema.String, uri: Schema.String }),
])
export const TextPart = Persistence.struct({ type: Schema.Literal("text"), ...PartBase })
export type TextPart = typeof TextPart.Type
export const FileAttachmentPart = Persistence.struct({
type: Schema.Literal("file"),
...PartBase,
path: Schema.String,
selection: Persistence.optional(FileSelection),
mime: Persistence.optional(Schema.String),
filename: Persistence.optional(Schema.String),
url: Persistence.optional(Schema.String),
source: Persistence.optional(FilePartSource),
})
export type FileAttachmentPart = typeof FileAttachmentPart.Type
export const AgentPart = Persistence.struct({ type: Schema.Literal("agent"), ...PartBase, name: Schema.String })
export type AgentPart = typeof AgentPart.Type
export const SkillPart = Persistence.struct({
type: Schema.Literal("skill"),
...PartBase,
id: Skill.ID,
name: Skill.Name,
})
export type SkillPart = typeof SkillPart.Type
const ImageFields = {
type: Schema.Literal("image"),
id: Schema.String,
filename: Schema.String,
sourcePath: Persistence.optional(Schema.String),
mime: Schema.String,
}
const Image = Persistence.struct({
...ImageFields,
blob: Schema.Struct({ id: Schema.NonEmptyString, url: Schema.String.check(Schema.isPattern(/^(blob:|data:)/)) }),
})
// Draft storage hydrates content-addressed blobs before this codec runs. Legacy
// inline data remains usable, but unresolved references are not renderable.
export const ImageAttachmentPart = Schema.Struct({
...ImageFields,
blob: Persistence.optional(
Schema.Struct({ id: Persistence.optional(Schema.String), url: Persistence.optional(Schema.String) }),
),
dataUrl: Persistence.optional(Schema.String),
}).pipe(
Schema.decodeTo(Schema.toType(Image), {
decode: SchemaGetter.transform((value) => {
const id = value.blob?.id ?? value.dataUrl ?? ""
const url = value.blob?.url
return {
type: value.type,
id: value.id,
filename: value.filename,
sourcePath: value.sourcePath,
mime: value.mime,
blob: {
id,
url: url?.startsWith("blob:") || url?.startsWith("data:") ? url : id.startsWith("data:") ? id : "",
},
}
}),
encode: SchemaGetter.transform((value) => value),
}),
)
export type ImageAttachmentPart = typeof ImageAttachmentPart.Type
export const ContentPart = Schema.Union([TextPart, FileAttachmentPart, AgentPart, SkillPart, ImageAttachmentPart])
export type ContentPart = typeof ContentPart.Type
export const Prompt = Persistence.array(ContentPart)
export type Prompt = typeof Prompt.Type
export const PromptModel = Persistence.struct({
providerID: Schema.String,
modelID: Schema.String,
variant: Persistence.optional(Schema.NullOr(Schema.String)),
})
export type PromptModel = typeof PromptModel.Type
export const FileContextItem = Persistence.struct({
type: Schema.Literal("file"),
path: Schema.String,
selection: Persistence.optional(FileSelection),
comment: Persistence.optional(Schema.String),
commentID: Persistence.optional(Schema.String),
commentOrigin: Persistence.optional(Schema.Literals(["review", "file"])),
preview: Persistence.optional(Schema.String),
})
export type FileContextItem = typeof FileContextItem.Type
export type ContextItem = FileContextItem
export function contextItemKey(item: ContextItem) {
const key = `${item.type}:${item.path}:${item.selection?.startLine}:${item.selection?.endLine}`
if (item.commentID) return `${key}:c=${item.commentID}`
const comment = item.comment?.trim()
if (!comment) return key
const digest = checksum(comment) ?? comment
return `${key}:c=${digest.slice(0, 8)}`
}
const ContextEntry = Schema.Struct({ ...FileContextItem.fields, key: Persistence.optional(Schema.String) }).pipe(
Schema.decodeTo(Persistence.struct({ ...FileContextItem.fields, key: Schema.String }).pipe(Schema.toType), {
decode: SchemaGetter.transform((item) => ({ ...item, key: contextItemKey(item) })),
encode: SchemaGetter.transform((item) => item),
}),
)
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export const ComposerStore = Persistence.struct({
prompt: Prompt.pipe(
Schema.decode({
decode: SchemaGetter.transform((prompt) =>
prompt.length ? prompt : DEFAULT_PROMPT.map((part) => ({ ...part })),
),
encode: SchemaGetter.transform((prompt) => prompt),
}),
),
cursor: Persistence.optional(
Schema.Finite.pipe(
Schema.decode({
decode: SchemaGetter.transform((cursor) => Math.max(0, cursor)),
encode: SchemaGetter.transform((cursor) => cursor),
}),
),
),
model: Persistence.optional(PromptModel),
mode: Persistence.optional(Schema.Literals(["normal", "shell"])),
retry: Persistence.optional(
Schema.Struct({
id: SessionMessage.ID,
agent: Schema.String,
providerID: Schema.String,
modelID: Schema.String,
variant: Persistence.optional(Schema.String),
}),
),
context: Persistence.struct({ items: Persistence.array(ContextEntry) }),
})
export type ComposerStore = typeof ComposerStore.Type
export const LineComment = Persistence.struct({
id: Schema.String,
file: Schema.String,
selection: SelectedLineRange,
comment: Schema.String,
time: Schema.Number,
})
export type LineComment = typeof LineComment.Type
export const CommentStore = Persistence.struct({
comments: Schema.Record(Schema.String, Schema.mutableKey(Persistence.array(LineComment))),
})
export type CommentStore = typeof CommentStore.Type
export const PromptHistoryComment = Persistence.struct({
id: Schema.String,
path: Schema.String,
selection: SelectedLineRange,
comment: Schema.String,
time: Schema.Number,
origin: Persistence.optional(Schema.Literals(["review", "file"])),
preview: Persistence.optional(Schema.String),
})
export type PromptHistoryComment = typeof PromptHistoryComment.Type
// History entries require a prompt array; only its individual parts recover.
const HistoryPrompt = Schema.Array(Persistence.fallback(Schema.UndefinedOr(ContentPart), () => undefined)).pipe(
Schema.decodeTo(Schema.toType(Prompt), {
decode: SchemaGetter.transform((parts) => parts.filter((part) => part !== undefined)),
encode: SchemaGetter.transform((parts) => parts),
}),
)
const HistoryEntry = Schema.Struct({ prompt: HistoryPrompt, comments: Persistence.array(PromptHistoryComment) })
export const PromptHistoryEntry = Schema.Union([HistoryEntry, HistoryPrompt]).pipe(
Schema.decodeTo(Schema.toType(HistoryEntry), {
decode: SchemaGetter.transform((entry) => ("prompt" in entry ? entry : { prompt: entry, comments: [] })),
encode: SchemaGetter.transform((entry) => entry),
}),
)
export type PromptHistoryEntry = typeof PromptHistoryEntry.Type
export const PromptHistoryState = Persistence.struct({ entries: Persistence.array(PromptHistoryEntry) })
+3 -8
View File
@@ -1,10 +1,7 @@
import { describe, expect, test } from "bun:test"
import { createRoot } from "solid-js"
import { Skill } from "@opencode-ai/schema/skill"
import { Schema, Option } from "effect"
import { Persistence } from "@/runtime/persistence/schema"
import { createMemoryComposerState, DEFAULT_PROMPT } from "./state"
import { ComposerStore } from "./schema"
import { createMemoryComposerState, DEFAULT_PROMPT, parseComposerStore } from "./state"
describe("prompt state initialization", () => {
test("initializes prompt text, cursor, and model together", () => {
@@ -32,9 +29,7 @@ describe("prompt state initialization", () => {
})
test("parses persisted state into one trusted current shape", () => {
const parsed = Schema.decodeUnknownSync(
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
)({
const parsed = parseComposerStore({
prompt: [
{ type: "text", content: "hello", start: 0, end: 5 },
{ type: "skill", id: "effect", name: "Effect", content: "@effect", start: 5, end: 12 },
@@ -110,6 +105,6 @@ describe("prompt state initialization", () => {
],
},
})
expect(Option.isNone(Schema.decodeUnknownOption(ComposerStore)("not an object"))).toBe(true)
expect(parseComposerStore("not an object")).toBeUndefined()
})
})
+343 -47
View File
@@ -1,41 +1,127 @@
import { batch, untrack, type Accessor } from "solid-js"
import { checksum } from "@opencode-ai/util/encode"
import { batch, type Accessor } from "solid-js"
import { createStore, type SetStoreFunction } from "solid-js/store"
import type { FileSelection } from "@/workspaces/files/model"
import { Persist, persisted } from "@/runtime/persistence/storage"
import { ServerScope } from "@/runtime/server/scope"
import type { BlobReference } from "@/runtime/persistence/drafts"
import type { Platform } from "@/runtime/platform/platform"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Skill } from "@opencode-ai/schema/skill"
import { clonePrompt } from "./prompt-parts"
import {
ComposerStore,
DEFAULT_PROMPT,
contextItemKey,
type ContextItem,
type FileContextItem,
type Prompt,
type PromptModel,
} from "./schema"
export { DEFAULT_PROMPT } from "./schema"
export type {
AgentPart,
ComposerStore,
ContentPart,
ContextItem,
FileAttachmentPart,
FileContextItem,
ImageAttachmentPart,
Prompt,
PromptModel,
SkillPart,
TextPart,
} from "./schema"
interface PartBase {
content: string
start: number
end: number
}
type FilePartSourceText = { value: string; start: number; end: number }
type FilePartSource =
| { text: FilePartSourceText; type: "file"; path: string }
| {
text: FilePartSourceText
type: "symbol"
path: string
range: { start: { line: number; character: number }; end: { line: number; character: number } }
name: string
kind: number
}
| { text: FilePartSourceText; type: "resource"; clientName: string; uri: string }
export interface TextPart extends PartBase {
type: "text"
}
export interface FileAttachmentPart extends PartBase {
type: "file"
path: string
selection?: FileSelection
mime?: string
filename?: string
url?: string
source?: FilePartSource
}
export interface AgentPart extends PartBase {
type: "agent"
name: string
}
export interface SkillPart extends PartBase {
type: "skill"
id: Skill.ID
name: Skill.Name
}
export interface ImageAttachmentPart {
type: "image"
id: string
filename: string
sourcePath?: string
mime: string
blob: BlobReference
}
export type ContentPart = TextPart | FileAttachmentPart | AgentPart | SkillPart | ImageAttachmentPart
export type Prompt = ContentPart[]
export type PromptModel = {
providerID: string
modelID: string
variant?: string | null
}
export type FileContextItem = {
type: "file"
path: string
selection?: FileSelection
comment?: string
commentID?: string
commentOrigin?: "review" | "file"
preview?: string
}
export type ContextItem = FileContextItem
export type PromptScope = { draftID: string } | { dir: string; id?: string }
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export type ComposerStore = {
prompt: Prompt
cursor?: number
model?: PromptModel
mode?: "normal" | "shell"
retry?: {
id: SessionMessage.ID
agent: string
providerID: string
modelID: string
variant?: string
}
context: {
items: (ContextItem & { key: string })[]
}
}
type InitialPrompt = {
prompt?: string
model?: PromptModel
}
function contextItemKey(item: ContextItem) {
if (item.type !== "file") return item.type
const start = item.selection?.startLine
const end = item.selection?.endLine
const key = `${item.type}:${item.path}:${start}:${end}`
if (item.commentID) return `${key}:c=${item.commentID}`
const comment = item.comment?.trim()
if (!comment) return key
const digest = checksum(comment) ?? comment
return `${key}:c=${digest.slice(0, 8)}`
}
export function isCommentItem(item: ContextItem | (ContextItem & { key: string })) {
return item.type === "file" && !!item.comment?.trim()
}
@@ -43,29 +129,34 @@ export function isCommentItem(item: ContextItem | (ContextItem & { key: string }
function createComposerActions(setStore: SetStoreFunction<ComposerStore>) {
return {
set(prompt: Prompt, cursorPosition?: number) {
batch(() =>
setStore({
prompt: clonePrompt(prompt),
...(cursorPosition !== undefined ? { cursor: cursorPosition } : {}),
retry: undefined,
}),
)
const next = clonePrompt(prompt)
batch(() => {
setStore("prompt", next)
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
setStore("retry", undefined)
})
},
reset() {
batch(() => setStore({ prompt: clonePrompt(DEFAULT_PROMPT), cursor: 0, retry: undefined }))
batch(() => {
setStore("prompt", clonePrompt(DEFAULT_PROMPT))
setStore("cursor", 0)
setStore("retry", undefined)
})
},
}
}
function composerTarget(serverScope: ServerScope, scope: PromptScope) {
return "draftID" in scope
? Persist.prompt(Persist.draft(scope.draftID, "prompt"))
: Persist.prompt({
...Persist.serverScoped(serverScope, scope.dir, scope.id, "prompt"),
...(serverScope === ServerScope.local
? { previousKey: `${scope.dir}/prompt${scope.id ? "/" + scope.id : ""}.v2` }
: {}),
})
const target =
"draftID" in scope
? Persist.prompt(Persist.draft(scope.draftID, "prompt"))
: Persist.prompt({
...Persist.serverScoped(serverScope, scope.dir, scope.id, "prompt"),
...(serverScope === ServerScope.local
? { previousKey: `${scope.dir}/prompt${scope.id ? "/" + scope.id : ""}.v2` }
: {}),
})
return { ...target, migrate: parseComposerStore }
}
function initialComposerStore(initial?: InitialPrompt): ComposerStore {
@@ -81,11 +172,206 @@ function initialComposerStore(initial?: InitialPrompt): ComposerStore {
}
}
export function parseComposerStore(value: unknown): ComposerStore | undefined {
if (!record(value)) return
const prompt = Array.isArray(value.prompt) ? value.prompt.flatMap(parsePart) : []
const context = record(value.context) && Array.isArray(value.context.items) ? value.context.items : []
const model = parseModel(value.model)
const retry = parseRetry(value.retry)
return {
prompt: prompt.length ? prompt : clonePrompt(DEFAULT_PROMPT),
...(typeof value.cursor === "number" && Number.isFinite(value.cursor) ? { cursor: Math.max(0, value.cursor) } : {}),
...(model ? { model } : {}),
...(value.mode === "normal" || value.mode === "shell" ? { mode: value.mode } : {}),
...(retry ? { retry } : {}),
context: {
items: context.flatMap((item) => {
const parsed = parseContextItem(item)
return parsed ? [{ ...parsed, key: contextItemKey(parsed) }] : []
}),
},
}
}
function parseRetry(value: unknown): ComposerStore["retry"] {
if (
!record(value) ||
typeof value.id !== "string" ||
!value.id.startsWith("msg_") ||
typeof value.agent !== "string" ||
typeof value.providerID !== "string" ||
typeof value.modelID !== "string"
) {
return
}
return {
id: SessionMessage.ID.make(value.id),
agent: value.agent,
providerID: value.providerID,
modelID: value.modelID,
...(typeof value.variant === "string" ? { variant: value.variant } : {}),
}
}
function parsePart(value: unknown): ContentPart[] {
if (!record(value) || typeof value.type !== "string") return []
if (value.type === "image") {
const legacy = typeof value.dataUrl === "string" ? value.dataUrl : undefined
const blobID = record(value.blob) && typeof value.blob.id === "string" ? value.blob.id : legacy
const hydrated = record(value.blob) && typeof value.blob.url === "string" ? value.blob.url : undefined
const blobURL =
hydrated?.startsWith("blob:") || hydrated?.startsWith("data:")
? hydrated
: blobID?.startsWith("data:")
? blobID
: undefined
if (
typeof value.id !== "string" ||
typeof value.filename !== "string" ||
typeof value.mime !== "string" ||
!blobID ||
!blobURL
) {
return []
}
return [
{
type: "image",
id: value.id,
filename: value.filename,
mime: value.mime,
blob: { id: blobID, url: blobURL },
...(typeof value.sourcePath === "string" ? { sourcePath: value.sourcePath } : {}),
},
]
}
if (typeof value.content !== "string" || typeof value.start !== "number" || typeof value.end !== "number") return []
if (value.type === "text") return [{ type: "text", content: value.content, start: value.start, end: value.end }]
if (value.type === "agent" && typeof value.name === "string") {
return [{ type: "agent", name: value.name, content: value.content, start: value.start, end: value.end }]
}
if (value.type === "skill" && typeof value.id === "string" && typeof value.name === "string") {
return [
{
type: "skill",
id: Skill.ID.make(value.id),
name: Skill.Name.make(value.name),
content: value.content,
start: value.start,
end: value.end,
},
]
}
if (value.type !== "file" || typeof value.path !== "string") return []
const selection = parseSelection(value.selection)
const source = parseSource(value.source)
return [
{
type: "file",
path: value.path,
content: value.content,
start: value.start,
end: value.end,
...(typeof value.mime === "string" ? { mime: value.mime } : {}),
...(typeof value.filename === "string" ? { filename: value.filename } : {}),
...(typeof value.url === "string" ? { url: value.url } : {}),
...(selection ? { selection } : {}),
...(source ? { source } : {}),
},
]
}
function parseContextItem(value: unknown): ContextItem | undefined {
if (!record(value) || value.type !== "file" || typeof value.path !== "string") return
const selection = parseSelection(value.selection)
const origin = value.commentOrigin === "review" || value.commentOrigin === "file" ? value.commentOrigin : undefined
return {
type: "file",
path: value.path,
...(selection ? { selection } : {}),
...(typeof value.comment === "string" ? { comment: value.comment } : {}),
...(typeof value.commentID === "string" ? { commentID: value.commentID } : {}),
...(origin ? { commentOrigin: origin } : {}),
...(typeof value.preview === "string" ? { preview: value.preview } : {}),
}
}
function parseModel(value: unknown): PromptModel | undefined {
if (!record(value) || typeof value.providerID !== "string" || typeof value.modelID !== "string") return
return {
providerID: value.providerID,
modelID: value.modelID,
...(typeof value.variant === "string" || value.variant === null ? { variant: value.variant } : {}),
}
}
function parseSelection(value: unknown): FileSelection | undefined {
if (!record(value)) return
if (
typeof value.startLine !== "number" ||
typeof value.startChar !== "number" ||
typeof value.endLine !== "number" ||
typeof value.endChar !== "number"
) {
return
}
return {
startLine: value.startLine,
startChar: value.startChar,
endLine: value.endLine,
endChar: value.endChar,
}
}
function parseSource(value: unknown): FilePartSource | undefined {
if (!record(value) || !record(value.text)) return
if (
typeof value.text.value !== "string" ||
typeof value.text.start !== "number" ||
typeof value.text.end !== "number"
) {
return
}
const text = { value: value.text.value, start: value.text.start, end: value.text.end }
if (value.type === "file" && typeof value.path === "string") return { type: "file", path: value.path, text }
if (value.type === "resource" && typeof value.clientName === "string" && typeof value.uri === "string") {
return { type: "resource", clientName: value.clientName, uri: value.uri, text }
}
if (
value.type !== "symbol" ||
typeof value.path !== "string" ||
typeof value.name !== "string" ||
typeof value.kind !== "number" ||
!record(value.range) ||
!record(value.range.start) ||
!record(value.range.end) ||
typeof value.range.start.line !== "number" ||
typeof value.range.start.character !== "number" ||
typeof value.range.end.line !== "number" ||
typeof value.range.end.character !== "number"
) {
return
}
return {
type: "symbol",
path: value.path,
name: value.name,
kind: value.kind,
text,
range: {
start: { line: value.range.start.line, character: value.range.start.character },
end: { line: value.range.end.line, character: value.range.end.character },
},
}
}
function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
function createComposerStateValue(store: ComposerStore, setStore: SetStoreFunction<ComposerStore>) {
const actions = createComposerActions(setStore)
const clearRetry = () => {
if (untrack(() => store.retry) !== undefined) setStore("retry", undefined)
}
const clearRetry = () => setStore("retry", undefined)
const value = {
store: [() => store, setStore] as [Accessor<ComposerStore>, SetStoreFunction<ComposerStore>],
current: () => store.prompt,
@@ -100,8 +386,8 @@ function createComposerStateValue(store: ComposerStore, setStore: SetStoreFuncti
mode: {
current: () => store.mode ?? "normal",
set: (mode: "normal" | "shell") => {
if (untrack(() => store.mode === mode && store.retry === undefined)) return
setStore({ mode, retry: undefined })
setStore("mode", mode)
clearRetry()
},
},
retry: {
@@ -156,7 +442,11 @@ function createPersistedComposer(
initial?: InitialPrompt,
platform?: Platform,
) {
const [store, setStore, _, ready] = persisted(target, ComposerStore, initialComposerStore(initial), platform)
const [store, setStore, _, ready] = persisted(
target,
createStore<ComposerStore>(initialComposerStore(initial)),
platform,
)
return { ready, ...createComposerStateValue(store, setStore) }
}
@@ -170,7 +460,13 @@ export function createComposerState(
}
export function createDraftComposerState(draftID: string, initial?: InitialPrompt) {
return createPersistedComposer(Persist.prompt(Persist.draft(draftID, "prompt")), initial)
return createPersistedComposer(
{
...Persist.prompt(Persist.draft(draftID, "prompt")),
migrate: parseComposerStore,
},
initial,
)
}
export type ComposerState = ReturnType<typeof createComposerState>
+4 -17
View File
@@ -1,5 +1,4 @@
import type { ComposerState, ContextItem, Prompt } from "./state"
import { appendPrompt, clonePrompt } from "./prompt-parts"
export type ComposerStateTarget = ReturnType<ComposerState["capture"]>
@@ -11,36 +10,24 @@ export function createComposerSubmission(input: {
const initial = input.target
let target = input.target
let cleared: Prompt | undefined
let following: Prompt | undefined
let preserveDraft = false
return {
prompt: input.prompt,
context: input.context,
target: () => target,
clear() {
if (initial !== target) {
initial.reset()
// A preparing session may already have an unsent follow-up in its promoted composer.
if (preserveDraft && target.current().some((part) => part.type === "image" || part.content.length > 0))
following = clonePrompt(target.current())
}
if (!following) target.reset()
if (initial !== target) initial.reset()
target.reset()
cleared = target.current()
},
retarget(next: ComposerStateTarget, options?: { preserveDraft?: boolean }) {
retarget(next: ComposerStateTarget) {
input.context.forEach((item) => next.context.add(item))
target = next
preserveDraft = options?.preserveDraft ?? false
},
current: (value: ComposerStateTarget) => target === value,
restore() {
if (cleared !== undefined && target.current() !== cleared) return
return {
target,
prompt: following ? appendPrompt(input.prompt, following) : input.prompt,
context: input.context,
}
return { target, prompt: input.prompt, context: input.context }
},
}
}
+1 -6
View File
@@ -231,7 +231,6 @@ describe("Composer submission", () => {
test("previews the first prompt while starting and hands it off before completing preparation", async () => {
const draft = createMemoryComposerState({ prompt: "prepare my worktree" }).capture()
const promoted = createMemoryComposerState().capture()
const preview = Promise.withResolvers<SessionMessageUser>()
const ready = Promise.withResolvers<void>()
const calls: string[] = []
@@ -248,10 +247,9 @@ describe("Composer submission", () => {
controls,
working: () => false,
submitted() {},
async start(_selection, submission, message) {
async start(_selection, _submission, message) {
preview.resolve(message)
await ready.promise
submission.retarget(promoted, { preserveDraft: true })
return {
session: target,
cleanupReady: Promise.resolve(),
@@ -259,8 +257,6 @@ describe("Composer submission", () => {
expect(handoff).toHaveLength(1)
expect(handoff[0]?.id).toBe(message.id)
expect(handoff[0]?.text).toBe("prepare my worktree")
expect(promoted.current()).toEqual([{ type: "text", content: "", start: 0, end: 0 }])
promoted.set([{ type: "text", content: "follow up", start: 0, end: 9 }], 9)
calls.push("complete")
},
}
@@ -275,7 +271,6 @@ describe("Composer submission", () => {
await submitted
expect(calls).toContain("complete")
expect(draft.current()).toEqual([{ type: "text", content: "", start: 0, end: 0 }])
expect(promoted.current()).toEqual([{ type: "text", content: "follow up", start: 0, end: 9 }])
})
test("does not restore a prompt already acknowledged by the durable inbox", async () => {
+1 -2
View File
@@ -248,8 +248,7 @@ function restoreSubmission(
preview: item.preview,
})),
)
// A recovered follow-up changes the payload, so it must use a new admission ID.
if (value.mode === "normal" && restored.prompt === submission.prompt) {
if (value.mode === "normal") {
restored.target.retry.set({
id: value.id,
agent: value.selection.agent,
-1
View File
@@ -73,7 +73,6 @@ if (root instanceof HTMLElement && root.dataset.opencodeMounted === undefined) {
const auth = authFromToken(new URLSearchParams(location.search).get("auth_token"))
clearAuthToken()
const standalone = isStandalone()
root.dataset.standalone = String(standalone)
if (standalone) restorePwaRoute()
const server: ServerConnection.Http = {
type: "http",
@@ -10,15 +10,10 @@ import { Persist, persisted } from "@/runtime/persistence/storage"
import { showToast } from "@/shell/notifications/toast"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { createResource } from "solid-js"
import { Schema } from "effect"
import { Persistence } from "@/runtime/persistence/schema"
import { createStore } from "solid-js/store"
import type { HomeController } from "../model"
import { useGlobal } from "@/runtime/server/runtime"
export const HomeServersSchema = Schema.Struct({
collapsed: Persistence.record(Persistence.fallback(Schema.Boolean, () => false)),
})
export function createHomeProjectsController(home: HomeController) {
const platform = usePlatform()
const pickDirectory = useDirectoryPicker()
@@ -27,7 +22,10 @@ export function createHomeProjectsController(home: HomeController) {
const openSettings = useSettingsCommand()
const serverManagement = useServerActionsController()
const global = useGlobal()
const [_state, setState, _, ready] = persisted(Persist.global("home.servers"), HomeServersSchema, { collapsed: {} })
const [_state, setState, _, ready] = persisted(
Persist.global("home.servers"),
createStore({ collapsed: {} as Record<string, boolean> }),
)
const [state] = createResource(
() => ready.promise ?? Promise.resolve(),
(promise) => promise.then(() => _state),
+2 -2
View File
@@ -702,11 +702,11 @@ function HomeSessionRow(
>
<Menu.Item onSelect={openEditor}>{props.language.t("common.rename")}</Menu.Item>
<Menu.Item onSelect={() => void props.onExportSession(props.server, props.record.session)}>
{props.language.t("common.export")}
{props.language.t("common.export")}...
</Menu.Item>
<Menu.Separator />
<Menu.Item onSelect={() => props.onDeleteSession(props.server, props.record.session)}>
{props.language.t("common.delete")}
{props.language.t("common.delete")}...
</Menu.Item>
</Menu.Content>
</Menu.Portal>
-5
View File
@@ -32,11 +32,6 @@
}
}
/* iOS can report navigator.standalone without matching display-mode. */
#root[data-standalone="true"] {
height: 100vh;
}
@layer components {
[data-slot="session-side-panel-presence"][data-opened="true"] {
animation: terminal-panel-presence-in 240ms cubic-bezier(0.22, 1, 0.36, 1);
@@ -117,13 +117,12 @@ export function createNewSessionComposerAdapter(props: {
{ dir: base64Encode(sessionDirectory), id: created.id },
{ server: server.key, scope: serverSDK.scope },
),
{ preserveDraft: !!pending },
)
})
return {
cleanupReady,
complete: pending ? () => pending.complete(submission.target()) : undefined,
complete: pending?.complete,
session: {
id: created.id,
directory: sessionDirectory,
@@ -195,7 +194,6 @@ async function resolveSessionDirectory(input: {
return createWorktree({
api: input.serverSDK.api,
data: input.data,
directory: input.projectDirectory,
project: input.data.location.info({ directory: input.projectDirectory })?.project,
branch: input.branch,
+3 -14
View File
@@ -3,7 +3,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Icon } from "@opencode-ai/ui/icon"
import { Wordmark } from "@opencode-ai/ui/wordmark"
import { Show, createMemo, createSignal } from "solid-js"
import { Schema } from "effect"
import { createStore } from "solid-js/store"
import createPresence from "solid-presence"
import { Composer } from "@/composer/composer"
import type { ComposerModel } from "@/composer/model"
@@ -20,19 +20,10 @@ import { useWorkspaceLocation } from "@/workspaces/location"
import { useProviders } from "@/providers/catalog/providers"
import { NEW_SESSION_CONTENT_WIDTH } from "@/new-session/layout"
import { Persist, persisted } from "@/runtime/persistence/storage"
import { Persistence } from "@/runtime/persistence/schema"
import type { NewSessionWorkspaceController } from "./workspace/controller"
const providerTipDismissalDuration = 30 * 24 * 60 * 60 * 1000
export const WorkspaceOnboardingSchema = Persistence.struct({
used: Schema.Boolean,
})
export const ProviderTipSchema = Persistence.struct({
dismissedAt: Schema.Finite,
})
export function NewSessionView(props: {
composer: ComposerModel
project: PromptProjectController
@@ -40,8 +31,7 @@ export function NewSessionView(props: {
}) {
const [onboarding, setOnboarding, , onboardingReady] = persisted(
Persist.global("workspace-onboarding"),
WorkspaceOnboardingSchema,
{ used: false },
createStore({ used: false }),
)
const select = (value: string) => {
props.workspace.selection.set(value)
@@ -120,8 +110,7 @@ function ProviderTip() {
const providers = useProviders(() => sdk().directory)
const [persistedState, setPersistedState, , persistedReady] = persisted(
Persist.global("new-session.provider-tip"),
ProviderTipSchema,
{ dismissedAt: 0 },
createStore({ dismissedAt: 0 }),
)
const visible = createMemo(
() =>
@@ -1,5 +1,10 @@
import { describe, expect, test } from "bun:test"
import { resolveNewSessionBranch, resolveNewSessionGit, resolveNewSessionWorktree } from "./controller"
import {
normalizeNewSessionWorktree,
resolveNewSessionBranch,
resolveNewSessionGit,
resolveNewSessionWorktree,
} from "./controller"
describe("new session workspace selection", () => {
test("uses main when the workspace bar is unavailable", () => {
@@ -7,6 +12,8 @@ describe("new session workspace selection", () => {
resolveNewSessionWorktree({
enabled: false,
selected: "/project/feature",
directory: "/project/feature",
projectWorktree: "/project",
}),
).toBe("main")
})
@@ -15,21 +22,31 @@ describe("new session workspace selection", () => {
expect(
resolveNewSessionWorktree({
enabled: true,
directory: "/project/feature",
projectWorktree: "/project",
fallback: "create",
}),
).toBe("create")
expect(
resolveNewSessionWorktree({
enabled: true,
directory: "/project/feature",
projectWorktree: "/project",
fallback: "main",
}),
).toBe("main")
).toBe("/project")
})
test("keeps local selection when the cached project path is stale", () => {
const input = { enabled: true, directory: "C:/Projects/repo", projectWorktree: "D:/Projects/repo" }
expect(resolveNewSessionWorktree(input)).toBe("main")
expect(resolveNewSessionWorktree({ ...input, selected: "/worktree" })).toBe("/worktree")
test("normalizes main to the project root outside the main worktree", () => {
expect(normalizeNewSessionWorktree("main", "/project/feature", "/project")).toBe("/project")
expect(normalizeNewSessionWorktree("main", "/project", "/project")).toBe("main")
})
test("treats equivalent Windows roots as the main worktree", () => {
expect(
resolveNewSessionWorktree({ enabled: true, directory: "C:\\Repo\\", projectWorktree: "c:/repo" }),
).toBe("main")
expect(normalizeNewSessionWorktree("main", "C:\\Repo\\", "c:/repo")).toBe("main")
})
test("resolves the branch from the active location", () => {
@@ -11,15 +11,27 @@ import { normalizeProjectInfo } from "@/runtime/server/global-sync/utils"
import {
isWorkspaceDirectory,
isWorkspaceSelection,
sameDirectory,
workspaceDefaultSelection,
workspaceDirectories,
workspaceSelectionDestination,
} from "@/workspaces/paths"
export function resolveNewSessionWorktree(input: { enabled: boolean; selected?: string; fallback?: string }) {
export function resolveNewSessionWorktree(input: {
enabled: boolean
selected?: string
directory: string
projectWorktree?: string
fallback?: string
}) {
if (!input.enabled) return "main"
if (input.selected) return input.selected
return input.fallback ?? "main"
return normalizeNewSessionWorktree(input.fallback ?? "main", input.directory, input.projectWorktree)
}
export function normalizeNewSessionWorktree(value: string, directory: string, projectWorktree?: string) {
if (value === "main" && projectWorktree && !sameDirectory(directory, projectWorktree)) return projectWorktree
return value
}
export function resolveNewSessionBranch(input: {
@@ -80,6 +92,8 @@ export function createNewSessionWorkspaceController(input: {
resolveNewSessionWorktree({
enabled: visible(),
selected: selected(),
directory: sdk().directory,
projectWorktree: currentProject()?.worktree,
fallback: fallback(),
}),
)
@@ -131,7 +145,7 @@ export function createNewSessionWorkspaceController(input: {
remember,
set: (worktree: string) => {
input.setSelectedBranch(undefined)
input.setSelectedWorktree(worktree)
input.setSelectedWorktree(normalizeNewSessionWorktree(worktree, sdk().directory, currentProject()?.worktree))
remember(worktree)
},
create: (branch: string) => {
@@ -0,0 +1,26 @@
import { describe, expect, test } from "bun:test"
import type { PluginInfo } from "@opencode-ai/client"
import { pluginLabels } from "./plugin"
describe("pluginLabels", () => {
test("omits built-in plugins", () => {
const plugins: PluginInfo[] = [
{ id: "opencode.internal", source: { type: "builtin" }, state: { status: "active" }, features: { server: true } },
{
id: "package-plugin",
source: { type: "package", target: "example" },
state: { status: "active" },
features: { server: true },
},
{
id: "local-plugin",
source: { type: "local", path: "/tmp/plugin.ts" },
state: { status: "active" },
features: { server: true },
},
{ id: "sdk-plugin", source: { type: "sdk" }, state: { status: "active" }, features: { server: true } },
]
expect(pluginLabels(plugins)).toEqual(["package-plugin", "local-plugin", "sdk-plugin"])
})
})
+32 -40
View File
@@ -3,12 +3,10 @@ import { base64Encode } from "@opencode-ai/util/encode"
import { useParams } from "@solidjs/router"
import { batch, createEffect, createMemo, startTransition } from "solid-js"
import { createStore } from "solid-js/store"
import { Schema, SchemaGetter } from "effect"
import { useModels } from "@/providers/models/models"
import { useSettings } from "@/settings/model"
import { useProviders } from "@/providers/catalog/providers"
import { Persist, persisted } from "@/runtime/persistence/storage"
import { Persistence } from "@/runtime/persistence/schema"
import { hasCustomAgent, resolveAgent } from "./agent"
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./variant"
import { useWorkspaceLocation } from "@/workspaces/location"
@@ -17,49 +15,39 @@ import { normalizeAgentList } from "@/runtime/server/global-sync/utils"
import { useServerSDK } from "@/runtime/server/client"
import { ScopedKey, type ServerScope } from "@/runtime/server/scope"
const ModelKeySchema = Schema.Struct({
providerID: Schema.String,
modelID: Schema.String,
variant: Schema.optional(Schema.String),
})
export type ModelKey = typeof ModelKeySchema.Type
export type ModelKey = { providerID: string; modelID: string; variant?: string }
const StateSchema = Schema.Struct({
agent: Persistence.optional(Schema.String),
model: Persistence.optional(ModelKeySchema),
variant: Persistence.optional(Schema.NullOr(Schema.String)),
})
type State = typeof StateSchema.Type
type State = {
agent?: string
model?: ModelKey
variant?: string | null
}
const SessionsSchema = Schema.Record(
Schema.String,
Schema.mutableKey(Persistence.fallback(Schema.UndefinedOr(StateSchema), () => undefined)),
)
const Current = Persistence.struct({ session: SessionsSchema })
export const ModelSelectionSchema = Persistence.migrate(
Current,
Schema.Struct({
session: Persistence.optional(Schema.Record(Schema.String, Schema.Unknown)),
pick: Persistence.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).pipe(
Schema.decode({
decode: SchemaGetter.transform((value) => ({
session:
value.session ??
Object.fromEntries(Object.entries(value.pick ?? {}).filter(([key]) => key !== WORKSPACE_KEY)),
})),
encode: SchemaGetter.transform((value) => value),
}),
),
)
type Saved = {
session: Record<string, State | undefined>
}
const WORKSPACE_KEY = "__workspace__"
const handoff = new Map<string, State>()
const handoffKey = (scope: ServerScope, dir: string, id: string) => ScopedKey.from(scope, dir, id)
const migrate = (value: unknown) => {
if (!value || typeof value !== "object") return { session: {} }
const item = value as {
session?: Record<string, State | undefined>
pick?: Record<string, State | undefined>
}
if (item.session && typeof item.session === "object") return { session: item.session }
if (!item.pick || typeof item.pick !== "object") return { session: {} }
return {
session: Object.fromEntries(Object.entries(item.pick).filter(([key]) => key !== WORKSPACE_KEY)),
}
}
const clone = (value: State | undefined) => {
if (!value) return
return {
@@ -89,9 +77,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
const [saved, setSaved, , savedReady] = persisted(
Persist.serverWorkspace(serverSDK.scope, sdk().directory, "model-selection"),
ModelSelectionSchema,
{ session: {} },
{
...Persist.serverWorkspace(serverSDK.scope, sdk().directory, "model-selection"),
migrate,
},
createStore<Saved>({
session: {},
}),
)
const [store, setStore] = createStore<{
+15 -30
View File
@@ -1,40 +1,25 @@
import { createMemo, createSignal, onCleanup, type Accessor } from "solid-js"
import { createMemo, type Accessor } from "solid-js"
import createPresence from "solid-presence"
export function createAnimatedPresence<T>(
value: Accessor<T | undefined>,
element: Accessor<HTMLElement | null>,
identity?: Accessor<unknown>,
minimumDuration = 0,
) {
const [tick, setTick] = createSignal(0)
const animation = createMemo<{
identity?: unknown
show: boolean
animate: boolean
value: T | undefined
started: number
}>((previous) => {
tick()
const currentIdentity = identity?.()
const current = value()
const same = !identity || previous?.identity === currentIdentity
const started = same && previous?.show ? previous.started : performance.now()
const remaining =
current === undefined && same && previous?.show ? minimumDuration - (performance.now() - started) : 0
const show = current !== undefined || remaining > 0
if (remaining > 0) {
const timer = setTimeout(() => setTick((value) => value + 1), remaining)
onCleanup(() => clearTimeout(timer))
}
return {
identity: currentIdentity,
show,
started,
animate: previous !== undefined && same && (previous.animate || previous.show !== show),
value: current ?? (same ? previous?.value : undefined),
}
})
const animation = createMemo<{ identity?: unknown; show: boolean; animate: boolean; value: T | undefined }>(
(previous) => {
const currentIdentity = identity?.()
const current = value()
const show = current !== undefined
const same = !identity || previous?.identity === currentIdentity
return {
identity: currentIdentity,
show,
animate: previous !== undefined && same && (previous.animate || previous.show !== show),
value: current ?? (same ? previous?.value : undefined),
}
},
)
const presence = createPresence({ show: () => animation().show, element })
return {
...presence,
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "ሂድ",
"desktop.menu.window": "መስኮት",
"desktop.menu.help": "እገዛ",
"desktop.menu.checkForUpdates": "ዝማኔዎችን ይመልከቱ",
"desktop.menu.checkForUpdates": "ዝማኔዎችን ይመልከቱ...",
"desktop.menu.settings": "ቅንጅቶች",
"desktop.menu.reloadWebview": "ዳግም ጫን Webview",
"desktop.menu.restart": "ዳግም አስጀምር",
"desktop.menu.exportLogs": "የመላክ ምዝግብ ማስታወሻዎች",
"desktop.menu.exportLogs": "የመላክ ምዝግብ ማስታወሻዎች...",
"desktop.menu.newSession": "አዲስ ክፍለ ጊዜ",
"desktop.menu.openProject": "ክፍት ፕሮጀክት",
"desktop.menu.openProject": "ክፍት ፕሮጀክት...",
"desktop.menu.newWindow": "አዲስ መስኮት",
"desktop.menu.closeWindow": "መስኮትን ዝጋ",
"desktop.menu.undo": "ቀልብስ",
@@ -212,8 +212,8 @@ export const dict = {
"provider.connect.method.apiKey": "API ቁልፍ",
"provider.connect.method.browser": "አሳሽ",
"provider.connect.method.headless": "ያለ ግራፊክ በይነገጽ",
"provider.connect.status.inProgress": "ፍቃድ በሂደት ላይ",
"provider.connect.status.waiting": "ፈቀዳን በመጠበቅ ላይ",
"provider.connect.status.inProgress": "ፍቃድ በሂደት ላይ...",
"provider.connect.status.waiting": "ፈቀዳን በመጠበቅ ላይ...",
"provider.connect.status.failed": "ፍቃድ መስጠት አልተሳካም፡ {{error}}",
"provider.connect.apiKey.description":
"መለያዎን ለማገናኘት የ{{provider}} API ቁልፍዎን ያስገቡ እና {{provider}} ሞዴሎችን በOpenCode ይጠቀሙ።",
@@ -303,7 +303,7 @@ export const dict = {
"common.goBack": "ወደ ኋላ ዳስስ",
"common.goForward": "ወደ ፊት ዳስስ",
"common.loading": "በመጫን ላይ",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "ሰርዝ",
"common.clear": "ግልጽ",
"common.open": "ክፍት",
@@ -312,12 +312,12 @@ export const dict = {
"common.continue": "ቀጥል",
"common.submit": "አስረክብ",
"common.save": "አስቀምጥ",
"common.saving": "በማስቀመጥ ላይ",
"common.saving": "በማስቀመጥ ላይ...",
"common.default": "ነባሪ",
"common.attachment": "አባሪ",
"prompt.placeholder.shell": "የሼል ትዕዛዝ አስገባ {{example}}",
"prompt.placeholder.normal": 'ማንኛውንም ነገር ይጠይቁ "{{example}}"',
"prompt.placeholder.simple": "ማንኛውንም ነገር ይጠይቁ",
"prompt.placeholder.shell": "የሼል ትዕዛዝ አስገባ... {{example}}",
"prompt.placeholder.normal": 'ማንኛውንም ነገር ይጠይቁ... "{{example}}"',
"prompt.placeholder.simple": "ማንኛውንም ነገር ይጠይቁ...",
"prompt.placeholder.summarizeComments": "አስተያየቶችን ማጠቃለል…",
"prompt.placeholder.summarizeComment": "አስተያየቱን ማጠቃለል…",
"prompt.mode.shell": "ሼል",
@@ -337,7 +337,7 @@ export const dict = {
"prompt.example.10": "APIሰነድ ፍጠር",
"prompt.example.11": "ዳታቤዝ መጠይቆችን ያመቻቹ",
"prompt.example.12": "የግቤት ማረጋገጫን አክል",
"prompt.example.13": "አዲስ አካል ለ",
"prompt.example.13": "አዲስ አካል ለ...",
"prompt.example.14": "ይህን ፕሮጀክት እንዴት ማሰማራት እችላለሁ?",
"prompt.example.15": "ለምርጥ ልምዶች የእኔን ኮድ ይገምግሙ",
"prompt.example.16": "የስህተት አያያዝን ወደዚህ ተግባር ያክሉ",
@@ -348,7 +348,7 @@ export const dict = {
"prompt.example.21": "የስደት ስክሪፕት እንድጽፍ እርዳኝ",
"prompt.example.22": "ለዚህ የመጨረሻ ነጥብ መሸጎጫ ተግባራዊ ያድርጉ",
"prompt.example.23": "ወደዚህ ዝርዝር ገፅ ጨምር",
"prompt.example.24": "CLI ትዕዛዝ ፍጠር ለ",
"prompt.example.24": "CLI ትዕዛዝ ፍጠር ለ...",
"prompt.example.25": "አካባቢያዊ ተለዋዋጮች እዚህ እንዴት ይሰራሉ?",
"prompt.popover.emptyResults": "ምንም ተዛማጅ ውጤቶች የሉም",
"prompt.popover.emptyCommands": "ምንም ተዛማጅ ትዕዛዞች የሉም",
@@ -400,7 +400,7 @@ export const dict = {
"dialog.directory.parent": "ወላጅ",
"dialog.directory.readError": "ይህን አቃፊ ማንበብ አልተቻለም",
"app.server.unreachable": "{{server}}ን መድረስ አልተቻለም",
"app.server.retrying": "በራስ ሰር እንደገና በመሞከር ላይ",
"app.server.retrying": "በራስ ሰር እንደገና በመሞከር ላይ...",
"app.server.otherServers": "ሌሎች አገልጋዮች",
"dialog.server.title": "አገልጋዮች",
"dialog.server.description": "ይህ መተግበሪያ ከየትኛው OpenCode አገልጋይ ጋር እንደሚገናኝ ይቀይሩ።",
@@ -410,7 +410,7 @@ export const dict = {
"dialog.server.add.url": "የአገልጋይ አድራሻ",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "ከአገልጋይ ጋር መገናኘት አልተቻለም",
"dialog.server.add.checking": "በማጣራት ላይ",
"dialog.server.add.checking": "በማጣራት ላይ...",
"dialog.server.add.button": "አገልጋይ አክል",
"dialog.server.add.name": "የአገልጋይ ስም (አማራጭ)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -434,26 +434,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL አገልጋይ",
"wsl.server.retryStart": "እንደገና ጀምር",
"wsl.server.updating": "በማዘመን ላይ",
"wsl.server.updating": "በማዘመን ላይ...",
"wsl.onboarding.step.distro": "distro ምረጥ",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "መፈተሽ WSL",
"wsl.onboarding.checkingRuntime": "መፈተሽ WSL...",
"wsl.onboarding.restartRequired": "Windows WSLን መጫኑን ለመጨረስ ዳግም መጀመር ያስፈልገዋል።",
"wsl.onboarding.ready": "WSL ዝግጁ ነው።",
"wsl.onboarding.required": "WSLለመቀጠል ያስፈልጋል።",
"wsl.onboarding.checkingDistros": "Distros በመፈተሽ ላይ",
"wsl.onboarding.installingDistro": "በመጫን ላይ {{distro}}",
"wsl.onboarding.checkingDistro": "መፈተሽ {{distro}}",
"wsl.onboarding.listingDistros": "የዝርዝር ዲስትሮስ",
"wsl.onboarding.checkingDistros": "Distros በመፈተሽ ላይ...",
"wsl.onboarding.installingDistro": "በመጫን ላይ {{distro}}...",
"wsl.onboarding.checkingDistro": "መፈተሽ {{distro}}...",
"wsl.onboarding.listingDistros": "የዝርዝር ዲስትሮስ...",
"wsl.onboarding.distroReady": "{{distro}} ዝግጁ ነው።",
"wsl.onboarding.distroNotInstalled": "{{distro}} ገና አልተጫነም።",
"wsl.onboarding.openDistroOnce": "ማዋቀሩን ለመጨረስ {{distro}} አንድ ጊዜ ይክፈቱ።",
"wsl.onboarding.finishingDistro": "ማዋቀሩን ለ{{distro}} በማጠናቀቅ ላይ።",
"wsl.onboarding.pickDistro": "distro ይምረጡ ወይም ከታች ይጫኑ።",
"wsl.onboarding.checkingOpencode": "መፈተሽ OpenCode",
"wsl.onboarding.checkingOpencodeIn": "OpenCodeን በ{{distro}} ውስጥ በመፈተሽ ላይ",
"wsl.onboarding.updatingOpencode": "በማዘመን ላይ OpenCode",
"wsl.onboarding.updatingOpencodeIn": "በማዘመን ላይ OpenCode በ{{distro}}",
"wsl.onboarding.checkingOpencode": "መፈተሽ OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "OpenCodeን በ{{distro}} ውስጥ በመፈተሽ ላይ...",
"wsl.onboarding.updatingOpencode": "በማዘመን ላይ OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "በማዘመን ላይ OpenCode በ{{distro}}...",
"wsl.onboarding.updateOpencodeIn": "አዘምን OpenCode በ{{distro}}።",
"wsl.onboarding.updateOpencode": "አዘምን OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode በ{{distro}} ውስጥ ዝግጁ ነው።",
@@ -462,11 +462,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "ጫን OpenCode",
"wsl.onboarding.chooseDistroFirst": "መጀመሪያ ዳይስትሮ ይምረጡ።",
"wsl.onboarding.loadFailed": "WSL ሁኔታን መጫን አልተሳካም።",
"wsl.onboarding.loading": "በመጫን ላይ",
"wsl.onboarding.loading": "በመጫን ላይ...",
"wsl.onboarding.installedDistros": "የተጫነው distros",
"wsl.onboarding.checkAgain": "እንደገና ያረጋግጡ",
"wsl.onboarding.distroStatus.ready": "ዝግጁ",
"wsl.onboarding.distroStatus.checking": "በማጣራት ላይ",
"wsl.onboarding.distroStatus.checking": "በማጣራት ላይ...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCodeአልተጫነም",
"wsl.onboarding.distroStatus.missingTools": "የጠፋ bash፣ curl",
"wsl.onboarding.distroStatus.unsupported": "የማይደገፍ · WSL 2] ተጠቀም",
@@ -483,7 +483,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "ሁሉም የተጫኑ ዳይስትሮዎች ቀድሞ ተጨምረዋል።",
"wsl.onboarding.noDistros": "ገና ምንም distros አልተገኘም።",
"wsl.onboarding.install": "ጫን",
"wsl.onboarding.installing": "በመጫን ላይ",
"wsl.onboarding.installing": "በመጫን ላይ...",
"wsl.onboarding.installDistro": "distro ጫን",
"wsl.onboarding.searchDistros": "የፍለጋ distros",
"wsl.onboarding.wsl2Required": "WSL 2 ያስፈልጋል።",
@@ -495,7 +495,7 @@ export const dict = {
"wsl.onboarding.unknown": "ያልታወቀ",
"wsl.onboarding.desktopVersion": "ዴስክቶፕ {{version}}",
"wsl.onboarding.versionMismatch": "የተጫነው ሥሪት ከዴስክቶፕ መተግበሪያ ሥሪት ጋር አይዛመድም።",
"wsl.onboarding.adding": "በማከል ላይ",
"wsl.onboarding.adding": "በማከል ላይ...",
"dialog.project.edit.title": "ፕሮጀክት አርትዕ",
"dialog.project.edit.name": "ስም",
"dialog.project.edit.icon": "አዶ",
@@ -600,7 +600,7 @@ export const dict = {
"error.page.action.report": "ስህተት ሪፖርት አድርግ",
"error.page.action.reported": "ስህተት ሪፖርት ተደርጓል",
"error.page.action.exportLogs": "የመላክ ምዝግብ ማስታወሻዎች",
"error.page.action.checking": "በማጣራት ላይ",
"error.page.action.checking": "በማጣራት ላይ...",
"error.page.action.checkUpdates": "ዝማኔዎችን ይመልከቱ",
"error.page.action.updateTo": "ወደ {{version}} አዘምን",
"error.page.circular": "[Circular]",
@@ -675,12 +675,12 @@ export const dict = {
"session.review.filesChanged": "የተቀየሩ ፋይሎች {{count}}",
"session.review.change.one": "ለውጥ",
"session.review.change.other": "ለውጦች",
"session.review.loadingChanges": "ለውጦችን በመጫን ላይ",
"session.review.loadingChanges": "ለውጦችን በመጫን ላይ...",
"session.review.empty": "በዚህ ክፍለ ጊዜ ምንም ለውጦች የሉም",
"session.review.noVcs": "ምንም Git የስሪት ቁጥጥር ስርዓት አልተገኘም፣ ለውጦች አይታዩም",
"session.review.noVcs.createGit.title": "Git ማከማቻ ፍጠር",
"session.review.noVcs.createGit.description": "በዚህ ፕሮጀክት ላይ ለውጦችን ይከታተሉ፣ ይገምግሙ እና ይቀልብሱ",
"session.review.noVcs.createGit.actionLoading": "Git ማከማቻ በመፍጠር ላይ",
"session.review.noVcs.createGit.actionLoading": "Git ማከማቻ በመፍጠር ላይ...",
"session.review.noVcs.createGit.action": "Git ማከማቻ ፍጠር",
"session.review.noSnapshot": "የቅጽበታዊ ገጽ እይታን መከታተል በውቅረት ውስጥ ተሰናክሏል፣ስለዚህ የክፍለ-ጊዜ ለውጦች አይገኙም",
"session.review.noChanges": "ምንም ለውጦች የሉም",
@@ -691,9 +691,9 @@ export const dict = {
"session.files.empty": "ምንም ፋይሎች",
"session.files.binaryContent": "ሁለትዮሽ ፋይል (ይዘት ሊታይ አይችልም)",
"session.messages.renderEarlier": "ቀደምት መልዕክቶችን ይስሩ",
"session.messages.loadingEarlier": "የቀድሞ መልዕክቶችን በመጫን ላይ",
"session.messages.loadingEarlier": "የቀድሞ መልዕክቶችን በመጫን ላይ...",
"session.messages.loadEarlier": "የቀድሞ መልዕክቶችን ጫን",
"session.messages.loading": "መልእክቶችን በመጫን ላይ",
"session.messages.loading": "መልእክቶችን በመጫን ላይ...",
"session.messages.jumpToLatest": "ወደ የቅርብ ጊዜ ዝለል",
"session.context.addToContext": "{{selection}}ን ወደ አውድ አክል",
"session.todo.title": "የሚደረጉ ሥራዎች",
@@ -754,8 +754,8 @@ export const dict = {
"common.copied": "የተገለበጠ",
"lsp.tooltip.none": "ምንም LSP አገልጋዮች",
"lsp.label.connected": "{{count}}LSP",
"prompt.loading": "ፕሮምፕትን በመጫን ላይ",
"terminal.loading": "በመጫን ላይ ተርሚናል",
"prompt.loading": "ፕሮምፕትን በመጫን ላይ...",
"terminal.loading": "በመጫን ላይ ተርሚናል...",
"terminal.title": "ተርሚናል",
"terminal.title.numbered": "ተርሚናል {{number}}",
"terminal.close": "ተርሚናል ዝጋ",
@@ -908,9 +908,9 @@ export const dict = {
"settings.updates.row.check.title": "ዝማኔዎችን ይመልከቱ",
"settings.updates.row.check.description": "ዝማኔዎችን ለማግኘት በእጅ ያረጋግጡ እና ካለ ይጫኑ",
"settings.updates.action.checkNow": "አሁን ያረጋግጡ",
"settings.updates.action.checking": "በማጣራት ላይ",
"settings.updates.action.downloading": "በማውረድ ላይ",
"settings.updates.action.installing": "በመጫን ላይ",
"settings.updates.action.checking": "በማጣራት ላይ...",
"settings.updates.action.downloading": "በማውረድ ላይ...",
"settings.updates.action.installing": "በመጫን ላይ...",
"settings.updates.toast.latest.title": "ዘመኑን ጠብቀዋል",
"settings.updates.toast.latest.description": "የቅርብ ጊዜውን የOpenCode እትም እያሄዱ ነው።",
"sound.option.none": "ምንም",
@@ -1056,7 +1056,7 @@ export const dict = {
"workspace.reset.success.title": "Workspace ዳግም ተጀምሯል",
"workspace.reset.success.description": "Workspace አሁን ከነባሪው ቅርንጫፍ ጋር ይዛመዳል።",
"workspace.error.stillPreparing": "Workspace አሁንም በመዘጋጀት ላይ ነው",
"workspace.status.checking": "ያልተጣመሩ ለውጦችን በመፈተሽ ላይ",
"workspace.status.checking": "ያልተጣመሩ ለውጦችን በመፈተሽ ላይ...",
"workspace.status.error": "git ሁኔታን ማረጋገጥ አልተቻለም።",
"workspace.status.clean": "ምንም ያልተዋሃዱ ለውጦች አልተገኙም።",
"workspace.status.dirty": "ያልተቀላቀሉ ለውጦች በዚህ workspace ላይ ተገኝተዋል።",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "انتقال",
"desktop.menu.window": "نافذة",
"desktop.menu.help": "مساعدة",
"desktop.menu.checkForUpdates": "التحقق من وجود تحديثات",
"desktop.menu.checkForUpdates": "التحقق من وجود تحديثات...",
"desktop.menu.settings": "الإعدادات",
"desktop.menu.reloadWebview": "إعادة تحميل Webview",
"desktop.menu.restart": "إعادة التشغيل",
"desktop.menu.exportLogs": "تصدير السجلات",
"desktop.menu.exportLogs": "تصدير السجلات...",
"desktop.menu.newSession": "جلسة جديدة",
"desktop.menu.openProject": "فتح مشروع",
"desktop.menu.openProject": "فتح مشروع...",
"desktop.menu.newWindow": "نافذة جديدة",
"desktop.menu.closeWindow": "إغلاق النافذة",
"desktop.menu.undo": "تراجع",
@@ -219,8 +219,8 @@ export const dict = {
"provider.connect.method.apiKey": "مفتاح API",
"provider.connect.method.browser": "المتصفح",
"provider.connect.method.headless": "بدون واجهة رسومية",
"provider.connect.status.inProgress": "جارٍ التفويض",
"provider.connect.status.waiting": "في انتظار التفويض",
"provider.connect.status.inProgress": "جارٍ التفويض...",
"provider.connect.status.waiting": "في انتظار التفويض...",
"provider.connect.status.failed": "فشل التفويض: {{error}}",
"provider.connect.apiKey.description":
"أدخل مفتاح API الخاص بـ {{provider}} لتوصيل حسابك واستخدام نماذج {{provider}} في OpenCode.",
@@ -313,7 +313,7 @@ export const dict = {
"common.goBack": "رجوع",
"common.goForward": "التقدم",
"common.loading": "جارٍ التحميل",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "إلغاء",
"common.clear": "مسح",
"common.connect": "اتصال",
@@ -321,12 +321,12 @@ export const dict = {
"common.continue": "متابعة",
"common.submit": "إرسال",
"common.save": "حفظ",
"common.saving": "جارٍ الحفظ",
"common.saving": "جارٍ الحفظ...",
"common.default": "افتراضي",
"common.attachment": "مرفق",
"prompt.placeholder.shell": "أدخل أمر shell {{example}}",
"prompt.placeholder.normal": 'اسأل أي شيء "{{example}}"',
"prompt.placeholder.simple": "اسأل أي شيء",
"prompt.placeholder.shell": "أدخل أمر shell... {{example}}",
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
"prompt.placeholder.simple": "اسأل أي شيء...",
"prompt.placeholder.summarizeComments": "لخّص التعليقات…",
"prompt.placeholder.summarizeComment": "لخّص التعليق…",
"prompt.mode.shell": "Shell",
@@ -346,7 +346,7 @@ export const dict = {
"prompt.example.10": "توليد وثائق API",
"prompt.example.11": "تحسين استعلامات قاعدة البيانات",
"prompt.example.12": "إضافة التحقق من صحة الإدخال",
"prompt.example.13": "إنشاء مكون جديد لـ",
"prompt.example.13": "إنشاء مكون جديد لـ...",
"prompt.example.14": "كيف أقوم بنشر هذا المشروع؟",
"prompt.example.15": "مراجعة الكود الخاص بي لأفضل الممارسات",
"prompt.example.16": "إضافة معالجة الأخطاء لهذه الدالة",
@@ -357,7 +357,7 @@ export const dict = {
"prompt.example.21": "ساعدني في كتابة برنامج نصي للترحيل",
"prompt.example.22": "تنفيذ التخزين المؤقت لنقطة النهاية هذه",
"prompt.example.23": "إضافة ترقيم الصفحات إلى هذه القائمة",
"prompt.example.24": "إنشاء أمر CLI لـ",
"prompt.example.24": "إنشاء أمر CLI لـ...",
"prompt.example.25": "كيف تعمل متغيرات البيئة هنا؟",
"prompt.popover.emptyResults": "لا توجد نتائج مطابقة",
"prompt.popover.emptyCommands": "لا توجد أوامر مطابقة",
@@ -416,7 +416,7 @@ export const dict = {
"dialog.server.add.url": "عنوان URL للخادم",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "تعذر الاتصال بالخادم",
"dialog.server.add.checking": "جارٍ التحقق",
"dialog.server.add.checking": "جارٍ التحقق...",
"dialog.server.add.button": "إضافة خادم",
"dialog.server.add.name": "اسم الخادم (اختياري)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -440,26 +440,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "خادم WSL",
"wsl.server.retryStart": "إعادة محاولة التشغيل",
"wsl.server.updating": "جارٍ التحديث",
"wsl.server.updating": "جارٍ التحديث...",
"wsl.onboarding.step.distro": "اختر توزيعة",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL",
"wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL...",
"wsl.onboarding.restartRequired": "يحتاج Windows إلى إعادة التشغيل لإكمال تثبيت WSL.",
"wsl.onboarding.ready": "WSL جاهز.",
"wsl.onboarding.required": "يلزم WSL للمتابعة.",
"wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات",
"wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}",
"wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}",
"wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات",
"wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات...",
"wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}...",
"wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}...",
"wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات...",
"wsl.onboarding.distroReady": "{{distro}} جاهزة.",
"wsl.onboarding.distroNotInstalled": "لم يتم تثبيت {{distro}} بعد.",
"wsl.onboarding.openDistroOnce": "افتح {{distro}} مرة واحدة لإكمال الإعداد.",
"wsl.onboarding.finishingDistro": "جارٍ إكمال إعداد {{distro}}.",
"wsl.onboarding.pickDistro": "اختر توزيعة أو ثبّت واحدة أدناه.",
"wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode",
"wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}",
"wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode",
"wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}",
"wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}...",
"wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "حدّث OpenCode في {{distro}}.",
"wsl.onboarding.updateOpencode": "تحديث OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode جاهز في {{distro}}.",
@@ -468,11 +468,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "تثبيت OpenCode",
"wsl.onboarding.chooseDistroFirst": "اختر توزيعة أولاً.",
"wsl.onboarding.loadFailed": "فشل تحميل حالة WSL.",
"wsl.onboarding.loading": "جارٍ التحميل",
"wsl.onboarding.loading": "جارٍ التحميل...",
"wsl.onboarding.installedDistros": "التوزيعات المثبتة",
"wsl.onboarding.checkAgain": "تحقق مجددًا",
"wsl.onboarding.distroStatus.ready": "جاهزة",
"wsl.onboarding.distroStatus.checking": "جارٍ التحقق",
"wsl.onboarding.distroStatus.checking": "جارٍ التحقق...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode غير مثبت",
"wsl.onboarding.distroStatus.missingTools": "bash و curl مفقودان",
"wsl.onboarding.distroStatus.unsupported": "غير مدعومة · استخدم WSL 2",
@@ -490,7 +490,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "تمت إضافة جميع التوزيعات المثبتة بالفعل.",
"wsl.onboarding.noDistros": "لم يتم اكتشاف أي توزيعات بعد.",
"wsl.onboarding.install": "تثبيت",
"wsl.onboarding.installing": "جارٍ التثبيت",
"wsl.onboarding.installing": "جارٍ التثبيت...",
"wsl.onboarding.installDistro": "تثبيت توزيعة",
"wsl.onboarding.searchDistros": "البحث عن توزيعات",
"wsl.onboarding.wsl2Required": "يلزم WSL 2.",
@@ -502,7 +502,7 @@ export const dict = {
"wsl.onboarding.unknown": "غير معروف",
"wsl.onboarding.desktopVersion": "سطح المكتب {{version}}",
"wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.",
"wsl.onboarding.adding": "جارٍ الإضافة",
"wsl.onboarding.adding": "جارٍ الإضافة...",
"dialog.project.edit.title": "تحرير المشروع",
"dialog.project.edit.name": "الاسم",
"dialog.project.edit.icon": "أيقونة",
@@ -606,7 +606,7 @@ export const dict = {
"error.page.action.report": "الإبلاغ عن الخطأ",
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
"error.page.action.exportLogs": "تصدير السجلات",
"error.page.action.checking": "جارٍ التحقق",
"error.page.action.checking": "جارٍ التحقق...",
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
"error.page.action.updateTo": "تحديث إلى {{version}}",
"error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق OpenCode",
@@ -674,7 +674,7 @@ export const dict = {
"session.review.filesChanged": "الملفات المتغيرة: {{count}}",
"session.review.change.one": "تغيير",
"session.review.change.other": "تغييرات",
"session.review.loadingChanges": "جارٍ تحميل التغييرات",
"session.review.loadingChanges": "جارٍ تحميل التغييرات...",
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
"session.review.noChanges": "لا توجد تغييرات",
"session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد",
@@ -686,9 +686,9 @@ export const dict = {
"session.files.empty": "لا توجد ملفات",
"session.files.binaryContent": "ملف ثنائي (لا يمكن عرض المحتوى)",
"session.messages.renderEarlier": "عرض الرسائل السابقة",
"session.messages.loadingEarlier": "جارٍ تحميل الرسائل السابقة",
"session.messages.loadingEarlier": "جارٍ تحميل الرسائل السابقة...",
"session.messages.loadEarlier": "تحميل الرسائل السابقة",
"session.messages.loading": "جارٍ تحميل الرسائل",
"session.messages.loading": "جارٍ تحميل الرسائل...",
"session.messages.jumpToLatest": "الانتقال إلى الأحدث",
"session.context.addToContext": "إضافة {{selection}} إلى السياق",
"session.todo.title": "المهام",
@@ -732,8 +732,8 @@ export const dict = {
"common.copied": "تم النسخ",
"lsp.tooltip.none": "لا توجد خوادم LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "جارٍ تحميل الموجه",
"terminal.loading": "جارٍ تحميل المحطة الطرفية",
"prompt.loading": "جارٍ تحميل الموجه...",
"terminal.loading": "جارٍ تحميل المحطة الطرفية...",
"terminal.title": "محطة طرفية",
"terminal.title.numbered": "محطة طرفية {{number}}",
"terminal.close": "إغلاق المحطة الطرفية",
@@ -845,9 +845,9 @@ export const dict = {
"settings.updates.row.check.title": "التحقق من التحديثات",
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
"settings.updates.action.checkNow": "تحقق الآن",
"settings.updates.action.checking": "جارٍ التحقق",
"settings.updates.action.downloading": "جارٍ التنزيل",
"settings.updates.action.installing": "جارٍ التثبيت",
"settings.updates.action.checking": "جارٍ التحقق...",
"settings.updates.action.downloading": "جارٍ التنزيل...",
"settings.updates.action.installing": "جارٍ التثبيت...",
"settings.updates.toast.latest.title": "لديك أحدث إصدار",
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
"sound.option.none": "بلا",
@@ -991,7 +991,7 @@ export const dict = {
"workspace.reset.success.title": "تمت إعادة تعيين مساحة العمل",
"workspace.reset.success.description": "مساحة العمل تطابق الآن الفرع الافتراضي.",
"workspace.error.stillPreparing": "مساحة العمل لا تزال قيد الإعداد",
"workspace.status.checking": "التحقق من التغييرات غير المدمجة",
"workspace.status.checking": "التحقق من التغييرات غير المدمجة...",
"workspace.status.error": "تعذر التحقق من حالة Git.",
"workspace.status.clean": "لم يتم اكتشاف تغييرات غير مدمجة.",
"workspace.status.dirty": "تم اكتشاف تغييرات غير مدمجة في مساحة العمل هذه.",
@@ -1021,12 +1021,12 @@ export const dict = {
"settings.providers.custom.description": "أضف موفرًا متوافقًا مع OpenAI باستخدام عنوان URL الأساسي.",
"app.server.unreachable": "تعذر الوصول إلى {{server}}",
"app.server.retrying": "جارٍ إعادة المحاولة تلقائيًا",
"app.server.retrying": "جارٍ إعادة المحاولة تلقائيًا...",
"app.server.otherServers": "خوادم أخرى",
"dialog.server.add.passwordPlaceholder": "كلمة المرور",
"session.review.noVcs.createGit.title": "إنشاء مستودع Git",
"session.review.noVcs.createGit.description": "تتبع ومراجعة والتراجع عن التغييرات في هذا المشروع",
"session.review.noVcs.createGit.actionLoading": "جارٍ إنشاء مستودع Git",
"session.review.noVcs.createGit.actionLoading": "جارٍ إنشاء مستودع Git...",
"session.review.noVcs.createGit.action": "إنشاء مستودع Git",
"session.todo.progress": "تم إكمال {{done}} من {{total}} مهام",
"session.question.progress": "{{current}} من {{total}} أسئلة",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Keçid",
"desktop.menu.window": "Pəncərə",
"desktop.menu.help": "Kömək",
"desktop.menu.checkForUpdates": "Yeniləmələri yoxla",
"desktop.menu.checkForUpdates": "Yeniləmələri yoxla...",
"desktop.menu.settings": "Tənzimləmələr",
"desktop.menu.reloadWebview": "Webview-u yenidən yüklə",
"desktop.menu.restart": "Yenidən başlat",
"desktop.menu.exportLogs": "Jurnalları ixrac et",
"desktop.menu.exportLogs": "Jurnalları ixrac et...",
"desktop.menu.newSession": "Yeni sessiya",
"desktop.menu.openProject": "Layihəni aç",
"desktop.menu.openProject": "Layihəni aç...",
"desktop.menu.newWindow": "Yeni pəncərə",
"desktop.menu.closeWindow": "Pəncərəni bağla",
"desktop.menu.undo": "Geri al",
@@ -215,8 +215,8 @@ export const dict = {
"provider.connect.method.apiKey": "API açarı",
"provider.connect.method.browser": "Brauzer",
"provider.connect.method.headless": "Başsız",
"provider.connect.status.inProgress": "Avtorizasiya davam edir",
"provider.connect.status.waiting": "Avtorizasiya gözlənilir",
"provider.connect.status.inProgress": "Avtorizasiya davam edir...",
"provider.connect.status.waiting": "Avtorizasiya gözlənilir...",
"provider.connect.status.failed": "Avtorizasiya uğursuz oldu: {{error}}",
"provider.connect.apiKey.description":
"Hesabınızı qoşmaq və OpenCode-da {{provider}} modellərindən istifadə etmək üçün {{provider}} API açarınızı daxil edin.",
@@ -310,7 +310,7 @@ export const dict = {
"common.goBack": "Geri keç",
"common.goForward": "İrəli keç",
"common.loading": "Yüklənir",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Ləğv et",
"common.clear": "Təmizlə",
"common.open": "Aç",
@@ -319,12 +319,12 @@ export const dict = {
"common.continue": "Davam et",
"common.submit": "Göndər",
"common.save": "Saxla",
"common.saving": "Saxlanılır",
"common.saving": "Saxlanılır...",
"common.default": "Standart",
"common.attachment": "əlavə",
"prompt.placeholder.shell": "Qabıq əmrini daxil edin {{example}}",
"prompt.placeholder.normal": 'Nə istəsəniz soruşun "{{example}}"',
"prompt.placeholder.simple": "Nə istəsəniz soruşun",
"prompt.placeholder.shell": "Qabıq əmrini daxil edin... {{example}}",
"prompt.placeholder.normal": 'Nə istəsəniz soruşun... "{{example}}"',
"prompt.placeholder.simple": "Nə istəsəniz soruşun...",
"prompt.placeholder.summarizeComments": "Şərhləri xülasə et…",
"prompt.placeholder.summarizeComment": "Şərhi xülasə et…",
"prompt.mode.shell": "Shell",
@@ -344,7 +344,7 @@ export const dict = {
"prompt.example.10": "API sənədləri yarat",
"prompt.example.11": "Verilənlər bazası sorğularını optimallaşdır",
"prompt.example.12": "Giriş doğrulaması əlavə et",
"prompt.example.13": "Bunun üçün yeni komponent yarat",
"prompt.example.13": "Bunun üçün yeni komponent yarat...",
"prompt.example.14": "Bu layihəni necə yerləşdirə bilərəm?",
"prompt.example.15": "Kodumu qabaqcıl təcrübələr baxımından nəzərdən keçir",
"prompt.example.16": "Bu funksiyaya xəta idarəetməsi əlavə et",
@@ -355,7 +355,7 @@ export const dict = {
"prompt.example.21": "Miqrasiya skripti yazmağıma kömək et",
"prompt.example.22": "Bu endpoint üçün keşləmə tətbiq et",
"prompt.example.23": "Bu siyahıya səhifələmə əlavə et",
"prompt.example.24": "Bunun üçün CLI əmri yarat",
"prompt.example.24": "Bunun üçün CLI əmri yarat...",
"prompt.example.25": "Burada mühit dəyişənləri necə işləyir?",
"prompt.popover.emptyResults": "Uyğun nəticə yoxdur",
"prompt.popover.emptyCommands": "Uyğun əmr yoxdur",
@@ -407,7 +407,7 @@ export const dict = {
"dialog.directory.parent": "Üst qovluq",
"dialog.directory.readError": "Bu qovluğu oxumaq mümkün deyil",
"app.server.unreachable": "{{server}}-ə çatmaq mümkün olmadı",
"app.server.retrying": "Avtomatik olaraq yenidən cəhd edilir",
"app.server.retrying": "Avtomatik olaraq yenidən cəhd edilir...",
"app.server.otherServers": "Digər serverlər",
"dialog.server.title": "Serverlər",
"dialog.server.description": "Bu tətbiqin qoşulduğu OpenCode serverini dəyişdirin.",
@@ -417,7 +417,7 @@ export const dict = {
"dialog.server.add.url": "Server ünvanı",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Serverə qoşulmaq mümkün olmadı",
"dialog.server.add.checking": "Yoxlanılır",
"dialog.server.add.checking": "Yoxlanılır...",
"dialog.server.add.button": "Server əlavə et",
"dialog.server.add.name": "Server adı (istəyə bağlı)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -442,27 +442,27 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Yenidən başlamağa cəhd edin",
"wsl.server.updating": "Yenilənir",
"wsl.server.updating": "Yenilənir...",
"wsl.onboarding.step.distro": "Distribütiv seçin",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL yoxlanılır",
"wsl.onboarding.checkingRuntime": "WSL yoxlanılır...",
"wsl.onboarding.restartRequired":
"WSL-nin quraşdırılmasını başa çatdırmaq üçün Windows-un yenidən başlaması lazımdır.",
"wsl.onboarding.ready": "WSL hazırdır.",
"wsl.onboarding.required": "Davam etmək üçün WSL tələb olunur.",
"wsl.onboarding.checkingDistros": "Distribütivlər yoxlanılır",
"wsl.onboarding.installingDistro": "{{distro}} quraşdırılır",
"wsl.onboarding.checkingDistro": "{{distro}} yoxlanılır",
"wsl.onboarding.listingDistros": "Distribütivlər siyahılanır",
"wsl.onboarding.checkingDistros": "Distribütivlər yoxlanılır...",
"wsl.onboarding.installingDistro": "{{distro}} quraşdırılır...",
"wsl.onboarding.checkingDistro": "{{distro}} yoxlanılır...",
"wsl.onboarding.listingDistros": "Distribütivlər siyahılanır...",
"wsl.onboarding.distroReady": "{{distro}} hazırdır.",
"wsl.onboarding.distroNotInstalled": "{{distro}} hələ quraşdırılmayıb.",
"wsl.onboarding.openDistroOnce": "Quraşdırmanı bitirmək üçün {{distro}}-ni bir dəfə açın.",
"wsl.onboarding.finishingDistro": "{{distro}} üçün quraşdırma tamamlanır.",
"wsl.onboarding.pickDistro": "Distribütiv seçin və ya aşağıdakılardan birini quraşdırın.",
"wsl.onboarding.checkingOpencode": "OpenCode yoxlanılır",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}-də OpenCode yoxlanılır",
"wsl.onboarding.updatingOpencode": "OpenCode yenilənir",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}-də OpenCode yenilənir",
"wsl.onboarding.checkingOpencode": "OpenCode yoxlanılır...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}-də OpenCode yoxlanılır...",
"wsl.onboarding.updatingOpencode": "OpenCode yenilənir...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}-də OpenCode yenilənir...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}-də OpenCode-u yeniləyin.",
"wsl.onboarding.updateOpencode": "OpenCode-u yeniləyin",
"wsl.onboarding.opencodeReadyIn": "OpenCode {{distro}}-də hazırdır.",
@@ -471,11 +471,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "OpenCode quraşdırın",
"wsl.onboarding.chooseDistroFirst": "Əvvəlcə distribütiv seçin.",
"wsl.onboarding.loadFailed": "WSL vəziyyətini yükləmək alınmadı.",
"wsl.onboarding.loading": "Yüklənir",
"wsl.onboarding.loading": "Yüklənir...",
"wsl.onboarding.installedDistros": "Quraşdırılmış distribütivlər",
"wsl.onboarding.checkAgain": "Yenidən yoxlayın",
"wsl.onboarding.distroStatus.ready": "Hazır",
"wsl.onboarding.distroStatus.checking": "Yoxlanır",
"wsl.onboarding.distroStatus.checking": "Yoxlanır...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode quraşdırılmayıb",
"wsl.onboarding.distroStatus.missingTools": "Çatışmayan bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Dəstəklənmir · WSL 2 istifadə edin",
@@ -494,7 +494,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Bütün quraşdırılmış distribütivlər artıq əlavə edilib.",
"wsl.onboarding.noDistros": "Hələ distribütiv aşkarlanmayıb.",
"wsl.onboarding.install": "Quraşdırın",
"wsl.onboarding.installing": "Quraşdırılır",
"wsl.onboarding.installing": "Quraşdırılır...",
"wsl.onboarding.installDistro": "Distribütiv quraşdırın",
"wsl.onboarding.searchDistros": "Distribütivləri axtar",
"wsl.onboarding.wsl2Required": "WSL 2 tələb olunur.",
@@ -506,7 +506,7 @@ export const dict = {
"wsl.onboarding.unknown": "naməlum",
"wsl.onboarding.desktopVersion": "masaüstü {{version}}",
"wsl.onboarding.versionMismatch": "Quraşdırılmış versiya masaüstü proqram versiyasına uyğun gəlmir.",
"wsl.onboarding.adding": "Əlavə edilir",
"wsl.onboarding.adding": "Əlavə edilir...",
"dialog.project.edit.title": "Layihəni redaktə et",
"dialog.project.edit.name": "Ad",
"dialog.project.edit.icon": "İkon",
@@ -615,7 +615,7 @@ export const dict = {
"error.page.action.report": "Xəta bildirin",
"error.page.action.reported": "Xəta bildirildi",
"error.page.action.exportLogs": "Qeydləri ixrac edin",
"error.page.action.checking": "Yoxlanılır",
"error.page.action.checking": "Yoxlanılır...",
"error.page.action.checkUpdates": "Yeniləmələri yoxla",
"error.page.action.updateTo": "{{version}} versiyasına yenilə",
"error.page.circular": "[Dairəvi]",
@@ -695,12 +695,12 @@ export const dict = {
"session.review.filesChanged": "{{count}} fayl dəyişdi",
"session.review.change.one": "Dəyişiklik",
"session.review.change.other": "Dəyişikliklər",
"session.review.loadingChanges": "Dəyişikliklər yüklənir",
"session.review.loadingChanges": "Dəyişikliklər yüklənir...",
"session.review.empty": "Bu sessiyada hələ dəyişiklik yoxdur",
"session.review.noVcs": "Git versiyaya nəzarət sistemi aşkarlanmadı; sessiya dəyişiklikləri göstərilməyəcək",
"session.review.noVcs.createGit.title": "Git repozitoriyası yaradın",
"session.review.noVcs.createGit.description": "Bu layihədə dəyişiklikləri izləyin, nəzərdən keçirin və ləğv edin",
"session.review.noVcs.createGit.actionLoading": "Git repozitoriyası yaradılır",
"session.review.noVcs.createGit.actionLoading": "Git repozitoriyası yaradılır...",
"session.review.noVcs.createGit.action": "Git repozitoriyası yaradın",
"session.review.noSnapshot":
"Snapshot izləmə konfiqurasiyada deaktiv edilib, ona görə də sessiya dəyişiklikləri əlçatan deyil",
@@ -712,9 +712,9 @@ export const dict = {
"session.files.empty": "Fayl yoxdur",
"session.files.binaryContent": "İkili fayl (məzmun göstərilə bilmir)",
"session.messages.renderEarlier": "Əvvəlki mesajları göstər",
"session.messages.loadingEarlier": "Əvvəlki mesajlar yüklənir",
"session.messages.loadingEarlier": "Əvvəlki mesajlar yüklənir...",
"session.messages.loadEarlier": "Əvvəlki mesajları yüklə",
"session.messages.loading": "Mesajlar yüklənir",
"session.messages.loading": "Mesajlar yüklənir...",
"session.messages.jumpToLatest": "Ən sonuncuya keç",
"session.context.addToContext": "{{selection}} kontekstə əlavə et",
"session.todo.title": "Tapşırıqlar",
@@ -775,8 +775,8 @@ export const dict = {
"common.copied": "Kopyalandı",
"lsp.tooltip.none": "LSP server yoxdur",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Prompt yüklənir",
"terminal.loading": "Terminal yüklənir",
"prompt.loading": "Prompt yüklənir...",
"terminal.loading": "Terminal yüklənir...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Terminalı bağla",
@@ -941,9 +941,9 @@ export const dict = {
"settings.updates.row.check.title": "Yeniləmələri yoxla",
"settings.updates.row.check.description": "Əl ilə yeniləmələri yoxla və mövcud olduqda quraşdır",
"settings.updates.action.checkNow": "İndi yoxla",
"settings.updates.action.checking": "Yoxlanılır",
"settings.updates.action.downloading": "Yüklənir",
"settings.updates.action.installing": "Quraşdırılır",
"settings.updates.action.checking": "Yoxlanılır...",
"settings.updates.action.downloading": "Yüklənir...",
"settings.updates.action.installing": "Quraşdırılır...",
"settings.updates.toast.latest.title": "Ən son versiyadan istifadə edirsiniz",
"settings.updates.toast.latest.description": "OpenCode-un ən son versiyasından istifadə edirsiniz.",
"sound.option.none": "Yoxdur",
@@ -1090,7 +1090,7 @@ export const dict = {
"workspace.reset.success.title": "İş sahəsi sıfırlandı",
"workspace.reset.success.description": "İş sahəsi indi standart branch ilə uyğunlaşır.",
"workspace.error.stillPreparing": "İş sahəsi hələ hazırlanır",
"workspace.status.checking": "Birləşdirilməmiş dəyişikliklər yoxlanılır",
"workspace.status.checking": "Birləşdirilməmiş dəyişikliklər yoxlanılır...",
"workspace.status.error": "Git statusu doğrulana bilmədi.",
"workspace.status.clean": "Birləşdirilməmiş dəyişiklik aşkarlanmadı.",
"workspace.status.dirty": "Bu iş sahəsində birləşdirilməmiş dəyişikliklər aşkarlandı.",
+41 -41
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "върви",
"desktop.menu.window": "прозорец",
"desktop.menu.help": "Помощ",
"desktop.menu.checkForUpdates": "Проверете за актуализации",
"desktop.menu.checkForUpdates": "Проверете за актуализации...",
"desktop.menu.settings": "Настройки",
"desktop.menu.reloadWebview": "Презареди Webview",
"desktop.menu.restart": "Рестартирайте",
"desktop.menu.exportLogs": "Експортиране на регистрационни файлове",
"desktop.menu.exportLogs": "Експортиране на регистрационни файлове...",
"desktop.menu.newSession": "Нова сесия",
"desktop.menu.openProject": "Отворете проекта",
"desktop.menu.openProject": "Отворете проекта...",
"desktop.menu.newWindow": "Нов прозорец",
"desktop.menu.closeWindow": "Затваряне на прозореца",
"desktop.menu.undo": "Отмяна",
@@ -215,8 +215,8 @@ export const dict = {
"provider.connect.method.apiKey": "API ключ",
"provider.connect.method.browser": "Браузър",
"provider.connect.method.headless": "Без глава",
"provider.connect.status.inProgress": "В ход е оторизация",
"provider.connect.status.waiting": "Чака се оторизация",
"provider.connect.status.inProgress": "В ход е оторизация...",
"provider.connect.status.waiting": "Чака се оторизация...",
"provider.connect.status.failed": "Упълномощаването е неуспешно: {{error}}",
"provider.connect.apiKey.description":
"Въведете своя {{provider}} API ключ, за да свържете акаунта си и да използвате {{provider}} модели в OpenCode.",
@@ -310,7 +310,7 @@ export const dict = {
"common.goBack": "Навигирайте назад",
"common.goForward": "Навигирайте напред",
"common.loading": "Зарежда се",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Отказ",
"common.clear": "ясно",
"common.open": "Отворете",
@@ -319,14 +319,14 @@ export const dict = {
"common.continue": "Продължи",
"common.submit": "Изпратете",
"common.save": "Запазване",
"common.saving": "Запазва се",
"common.saving": "Запазва се...",
"common.default": "По подразбиране",
"common.attachment": "прикачен файл",
"prompt.placeholder.shell": "Въведете команда shell {{example}}",
"prompt.placeholder.normal": 'Попитайте каквото и да е "{{example}}"',
"prompt.placeholder.simple": "Питайте нещо",
"prompt.placeholder.summarizeComments": "Обобщете коментарите",
"prompt.placeholder.summarizeComment": "Обобщение на коментара",
"prompt.placeholder.shell": "Въведете команда shell... {{example}}",
"prompt.placeholder.normal": 'Попитайте каквото и да е... "{{example}}"',
"prompt.placeholder.simple": "Питайте нещо...",
"prompt.placeholder.summarizeComments": "Обобщете коментарите...",
"prompt.placeholder.summarizeComment": "Обобщение на коментара...",
"prompt.mode.shell": "Shell",
"prompt.mode.normal": "подкана",
"prompt.mode.shell.exit": "esc за изход",
@@ -344,7 +344,7 @@ export const dict = {
"prompt.example.10": "Генерирайте API документация",
"prompt.example.11": "Оптимизирайте заявките към базата данни",
"prompt.example.12": "Добавете проверка на входа",
"prompt.example.13": "Създайте нов компонент за",
"prompt.example.13": "Създайте нов компонент за...",
"prompt.example.14": "Как да разположа този проект?",
"prompt.example.15": "Прегледайте моя код за най-добри практики",
"prompt.example.16": "Добавете обработка на грешки към тази функция",
@@ -355,7 +355,7 @@ export const dict = {
"prompt.example.21": "Помогнете ми да напиша скрипт за миграция",
"prompt.example.22": "Приложете кеширане за тази крайна точка",
"prompt.example.23": "Добавете пагинация към този списък",
"prompt.example.24": "Създайте команда CLI за",
"prompt.example.24": "Създайте команда CLI за...",
"prompt.example.25": "Как работят променливите на средата тук?",
"prompt.popover.emptyResults": "Няма съответстващи резултати",
"prompt.popover.emptyCommands": "Няма съответстващи команди",
@@ -407,7 +407,7 @@ export const dict = {
"dialog.directory.parent": "Родител",
"dialog.directory.readError": "Тази папка не може да се прочете",
"app.server.unreachable": "Не може да се достигне до {{server}}",
"app.server.retrying": "Опитва се автоматично",
"app.server.retrying": "Опитва се автоматично...",
"app.server.otherServers": "Други сървъри",
"dialog.server.title": "Сървъри",
"dialog.server.description": "Превключете към кой OpenCode сървър се свързва това приложение.",
@@ -417,7 +417,7 @@ export const dict = {
"dialog.server.add.url": "Адрес на сървъра",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Не може да се свърже със сървъра",
"dialog.server.add.checking": "Проверка",
"dialog.server.add.checking": "Проверка...",
"dialog.server.add.button": "Добавете сървър",
"dialog.server.add.name": "Име на сървъра (по избор)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -442,26 +442,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL сървър",
"wsl.server.retryStart": "Повторен старт",
"wsl.server.updating": "Актуализиране",
"wsl.server.updating": "Актуализиране...",
"wsl.onboarding.step.distro": "Изберете дистрибуция",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Проверява се WSL",
"wsl.onboarding.checkingRuntime": "Проверява се WSL...",
"wsl.onboarding.restartRequired": "Windows се нуждае от рестартиране, за да завърши инсталирането на WSL.",
"wsl.onboarding.ready": "WSL е готов.",
"wsl.onboarding.required": "WSL се изисква, за да продължите.",
"wsl.onboarding.checkingDistros": "Дистрибуции се проверяват",
"wsl.onboarding.installingDistro": "Инсталиране на {{distro}}",
"wsl.onboarding.checkingDistro": "Проверява се {{distro}}",
"wsl.onboarding.listingDistros": "Списък на дистрибуции",
"wsl.onboarding.checkingDistros": "Дистрибуции се проверяват...",
"wsl.onboarding.installingDistro": "Инсталиране на {{distro}}...",
"wsl.onboarding.checkingDistro": "Проверява се {{distro}}...",
"wsl.onboarding.listingDistros": "Списък на дистрибуции...",
"wsl.onboarding.distroReady": "{{distro}} е готов.",
"wsl.onboarding.distroNotInstalled": "{{distro}} все още не е инсталиран.",
"wsl.onboarding.openDistroOnce": "Отворете {{distro}} веднъж, за да завършите настройката.",
"wsl.onboarding.finishingDistro": "Завършва настройката за {{distro}}.",
"wsl.onboarding.pickDistro": "Изберете дистрибуция или инсталирайте такава по-долу.",
"wsl.onboarding.checkingOpencode": "Проверява се OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Проверява се OpenCode в {{distro}}",
"wsl.onboarding.updatingOpencode": "Актуализиране на OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Актуализиране на OpenCode в {{distro}}",
"wsl.onboarding.checkingOpencode": "Проверява се OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Проверява се OpenCode в {{distro}}...",
"wsl.onboarding.updatingOpencode": "Актуализиране на OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Актуализиране на OpenCode в {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Актуализирайте OpenCode в {{distro}}.",
"wsl.onboarding.updateOpencode": "Актуализация OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode е готов в {{distro}}.",
@@ -470,11 +470,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Инсталирайте OpenCode",
"wsl.onboarding.chooseDistroFirst": "Първо изберете дистрибуция.",
"wsl.onboarding.loadFailed": "Неуспешно зареждане на състояние WSL.",
"wsl.onboarding.loading": "Зареждане",
"wsl.onboarding.loading": "Зареждане...",
"wsl.onboarding.installedDistros": "Инсталирани дистрибуции",
"wsl.onboarding.checkAgain": "Проверете отново",
"wsl.onboarding.distroStatus.ready": "Готови",
"wsl.onboarding.distroStatus.checking": "Проверка",
"wsl.onboarding.distroStatus.checking": "Проверка...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не е инсталиран",
"wsl.onboarding.distroStatus.missingTools": "Липсват bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Не се поддържа · Използвайте WSL 2",
@@ -493,7 +493,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Всички инсталирани дистрибуции вече са добавени.",
"wsl.onboarding.noDistros": "Все още няма открити дистрибуции.",
"wsl.onboarding.install": "Инсталирайте",
"wsl.onboarding.installing": "Инсталира се",
"wsl.onboarding.installing": "Инсталира се...",
"wsl.onboarding.installDistro": "Инсталирайте дистрибуция",
"wsl.onboarding.searchDistros": "Търсете дистрибуции",
"wsl.onboarding.wsl2Required": "WSL 2 е задължително.",
@@ -505,7 +505,7 @@ export const dict = {
"wsl.onboarding.unknown": "неизвестен",
"wsl.onboarding.desktopVersion": "работен плот {{version}}",
"wsl.onboarding.versionMismatch": "Инсталираната версия не съответства на версията на настолното приложение.",
"wsl.onboarding.adding": "Добавяне",
"wsl.onboarding.adding": "Добавяне...",
"dialog.project.edit.title": "Редактиране на проекта",
"dialog.project.edit.name": "Име",
"dialog.project.edit.icon": "Икона",
@@ -612,7 +612,7 @@ export const dict = {
"error.page.action.report": "Докладване за грешка",
"error.page.action.reported": "Съобщава се за грешка",
"error.page.action.exportLogs": "Експортиране на регистрационни файлове",
"error.page.action.checking": "Проверка",
"error.page.action.checking": "Проверка...",
"error.page.action.checkUpdates": "Проверете за актуализации",
"error.page.action.updateTo": "Актуализиране до {{version}}",
"error.page.circular": "[Кръгло]",
@@ -691,12 +691,12 @@ export const dict = {
"session.review.filesChanged": "Променени файлове {{count}}",
"session.review.change.one": "промяна",
"session.review.change.other": "Промени",
"session.review.loadingChanges": "Промените се зареждат",
"session.review.loadingChanges": "Промените се зареждат...",
"session.review.empty": "Все още няма промени в тази сесия",
"session.review.noVcs": "Не е открита Git Система за контрол на версиите, промените не се показват",
"session.review.noVcs.createGit.title": "Създайте Git хранилище",
"session.review.noVcs.createGit.description": "Проследявайте, преглеждайте и отменяйте промените в този проект",
"session.review.noVcs.createGit.actionLoading": "Създаване на Git хранилище",
"session.review.noVcs.createGit.actionLoading": "Създаване на Git хранилище...",
"session.review.noVcs.createGit.action": "Създайте Git хранилище",
"session.review.noSnapshot":
"Проследяването на моментна снимка е деактивирано в конфигурацията, така че промените в сесията не са налични",
@@ -708,9 +708,9 @@ export const dict = {
"session.files.empty": "Няма файлове",
"session.files.binaryContent": "Двоичен файл (съдържанието не може да се покаже)",
"session.messages.renderEarlier": "Изобразяване на по-ранни съобщения",
"session.messages.loadingEarlier": "Зареждат се предишни съобщения",
"session.messages.loadingEarlier": "Зареждат се предишни съобщения...",
"session.messages.loadEarlier": "Зареди по-ранни съобщения",
"session.messages.loading": "Съобщенията се зареждат",
"session.messages.loading": "Съобщенията се зареждат...",
"session.messages.jumpToLatest": "Преминете към най-новото",
"session.context.addToContext": "Добавете {{selection}} към контекста",
"session.todo.title": "Задачи",
@@ -771,8 +771,8 @@ export const dict = {
"common.copied": "Копирано",
"lsp.tooltip.none": "Няма LSP сървъри",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Подканата се зарежда",
"terminal.loading": "Терминалът се зарежда",
"prompt.loading": "Подканата се зарежда...",
"terminal.loading": "Терминалът се зарежда...",
"terminal.title": "Терминал",
"terminal.title.numbered": "Терминал {{number}}",
"terminal.close": "Затворете терминала",
@@ -939,9 +939,9 @@ export const dict = {
"settings.updates.row.check.title": "Проверете за актуализации",
"settings.updates.row.check.description": "Проверете ръчно за актуализации и инсталирайте, ако има такива",
"settings.updates.action.checkNow": "Проверете сега",
"settings.updates.action.checking": "Проверка",
"settings.updates.action.downloading": "Изтегля се",
"settings.updates.action.installing": "Инсталира се",
"settings.updates.action.checking": "Проверка...",
"settings.updates.action.downloading": "Изтегля се...",
"settings.updates.action.installing": "Инсталира се...",
"settings.updates.toast.latest.title": "Вие сте в крак с времето",
"settings.updates.toast.latest.description": "Вие използвате най-новата версия на OpenCode.",
"sound.option.none": "Няма",
@@ -1091,7 +1091,7 @@ export const dict = {
"workspace.reset.success.title": "Нулиране на работното пространство",
"workspace.reset.success.description": "Работното пространство вече съответства на клона по подразбиране.",
"workspace.error.stillPreparing": "Работното пространство все още се подготвя",
"workspace.status.checking": "Проверка за необединени промени",
"workspace.status.checking": "Проверка за необединени промени...",
"workspace.status.error": "Не може да се провери състоянието на git.",
"workspace.status.clean": "Не са открити необединени промени.",
"workspace.status.dirty": "Открити са необединени промени в това работно пространство.",
+41 -41
View File
@@ -6,13 +6,13 @@ export const dict: Record<string, string> = {
"desktop.menu.go": "যাও",
"desktop.menu.window": "জানালা",
"desktop.menu.help": "সাহায্য",
"desktop.menu.checkForUpdates": "আপডেটের জন্য চেক করুন",
"desktop.menu.checkForUpdates": "আপডেটের জন্য চেক করুন...",
"desktop.menu.settings": "সেটিংস",
"desktop.menu.reloadWebview": "Webview পুনরায় লোড করুন",
"desktop.menu.restart": "রিস্টার্ট করুন",
"desktop.menu.exportLogs": "লগ রপ্তানি করুন",
"desktop.menu.exportLogs": "লগ রপ্তানি করুন...",
"desktop.menu.newSession": "নতুন সেশন",
"desktop.menu.openProject": "প্রকল্প খুলুন",
"desktop.menu.openProject": "প্রকল্প খুলুন...",
"desktop.menu.newWindow": "নতুন উইন্ডো",
"desktop.menu.closeWindow": "উইন্ডো বন্ধ করুন",
"desktop.menu.undo": "পূর্বাবস্থায় ফেরান",
@@ -213,8 +213,8 @@ export const dict: Record<string, string> = {
"provider.connect.method.apiKey": "API কী",
"provider.connect.method.browser": "ব্রাউজার",
"provider.connect.method.headless": "মাথাবিহীন",
"provider.connect.status.inProgress": "অনুমোদন চলছে",
"provider.connect.status.waiting": "অনুমোদনের জন্য অপেক্ষা করা হচ্ছে",
"provider.connect.status.inProgress": "অনুমোদন চলছে...",
"provider.connect.status.waiting": "অনুমোদনের জন্য অপেক্ষা করা হচ্ছে...",
"provider.connect.status.failed": "অনুমোদন ব্যর্থ হয়েছে: {{error}}",
"provider.connect.apiKey.description":
"আপনার অ্যাকাউন্ট সংযোগ করতে আপনার {{provider}} API কী লিখুন এবং OpenCode-এ {{provider}} মডেলগুলি ব্যবহার করুন৷",
@@ -307,7 +307,7 @@ export const dict: Record<string, string> = {
"common.goBack": "ফিরে নেভিগেট করুন",
"common.goForward": "সামনে নেভিগেট করুন",
"common.loading": "লোড হচ্ছে",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "বাতিল করুন",
"common.clear": "পরিষ্কার",
"common.open": "খোলা",
@@ -316,14 +316,14 @@ export const dict: Record<string, string> = {
"common.continue": "চালিয়ে যান",
"common.submit": "জমা দিন",
"common.save": "সংরক্ষণ করুন",
"common.saving": "সংরক্ষণ করা হচ্ছে",
"common.saving": "সংরক্ষণ করা হচ্ছে...",
"common.default": "ডিফল্ট",
"common.attachment": "সংযুক্তি",
"prompt.placeholder.shell": "শেল কমান্ড লিখুন {{example}}",
"prompt.placeholder.normal": 'কিছু জিজ্ঞাসা করুন "{{example}}"',
"prompt.placeholder.simple": "কিছু জিজ্ঞাসা করুন",
"prompt.placeholder.summarizeComments": "মন্তব্য সংক্ষিপ্ত করুন",
"prompt.placeholder.summarizeComment": "মন্তব্য সংক্ষিপ্ত করুন",
"prompt.placeholder.shell": "শেল কমান্ড লিখুন... {{example}}",
"prompt.placeholder.normal": 'কিছু জিজ্ঞাসা করুন... "{{example}}"',
"prompt.placeholder.simple": "কিছু জিজ্ঞাসা করুন...",
"prompt.placeholder.summarizeComments": "মন্তব্য সংক্ষিপ্ত করুন...",
"prompt.placeholder.summarizeComment": "মন্তব্য সংক্ষিপ্ত করুন...",
"prompt.mode.shell": "শেল",
"prompt.mode.normal": "প্রম্পট",
"prompt.mode.shell.exit": "প্রস্থান করতে esc",
@@ -341,7 +341,7 @@ export const dict: Record<string, string> = {
"prompt.example.10": "API ডকুমেন্টেশন তৈরি করুন",
"prompt.example.11": "ডাটাবেস কোয়েরি অপ্টিমাইজ করুন",
"prompt.example.12": "ইনপুট বৈধতা যোগ করুন",
"prompt.example.13": "এর জন্য একটি নতুন উপাদান তৈরি করুন",
"prompt.example.13": "এর জন্য একটি নতুন উপাদান তৈরি করুন...",
"prompt.example.14": "আমি কিভাবে এই প্রকল্প স্থাপন করব?",
"prompt.example.15": "সেরা অনুশীলনের জন্য আমার কোড পর্যালোচনা করুন",
"prompt.example.16": "এই ফাংশনে ত্রুটি হ্যান্ডলিং যোগ করুন",
@@ -352,7 +352,7 @@ export const dict: Record<string, string> = {
"prompt.example.21": "আমাকে একটি মাইগ্রেশন স্ক্রিপ্ট লিখতে সাহায্য করুন",
"prompt.example.22": "এই শেষ পয়েন্টের জন্য ক্যাশিং প্রয়োগ করুন",
"prompt.example.23": "এই তালিকায় পৃষ্ঠা সংখ্যা যোগ করুন",
"prompt.example.24": "এর জন্য একটি CLI কমান্ড তৈরি করুন",
"prompt.example.24": "এর জন্য একটি CLI কমান্ড তৈরি করুন...",
"prompt.example.25": "পরিবেশ ভেরিয়েবল এখানে কিভাবে কাজ করে?",
"prompt.popover.emptyResults": "কোন মিল ফলাফল",
"prompt.popover.emptyCommands": "কোনো মিলিত কমান্ড নেই",
@@ -404,7 +404,7 @@ export const dict: Record<string, string> = {
"dialog.directory.parent": "অভিভাবক",
"dialog.directory.readError": "এই ফোল্ডারটি পড়তে অক্ষম৷",
"app.server.unreachable": "{{server}} এ পৌঁছানো যায়নি",
"app.server.retrying": "স্বয়ংক্রিয়ভাবে পুনরায় চেষ্টা করা হচ্ছে",
"app.server.retrying": "স্বয়ংক্রিয়ভাবে পুনরায় চেষ্টা করা হচ্ছে...",
"app.server.otherServers": "অন্যান্য সার্ভার",
"dialog.server.title": "সার্ভার",
"dialog.server.description": "এই অ্যাপটি কোন OpenCode সার্ভারের সাথে সংযোগ করে তা পরিবর্তন করুন।",
@@ -414,7 +414,7 @@ export const dict: Record<string, string> = {
"dialog.server.add.url": "সার্ভার ঠিকানা",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "সার্ভারের সাথে সংযোগ করা যায়নি",
"dialog.server.add.checking": "পরীক্ষা করা হচ্ছে",
"dialog.server.add.checking": "পরীক্ষা করা হচ্ছে...",
"dialog.server.add.button": "সার্ভার যোগ করুন",
"dialog.server.add.name": "সার্ভারের নাম (ঐচ্ছিক)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -439,26 +439,26 @@ export const dict: Record<string, string> = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL সার্ভার",
"wsl.server.retryStart": "আবার শুরু করার চেষ্টা করুন",
"wsl.server.updating": "আপডেট হচ্ছে",
"wsl.server.updating": "আপডেট হচ্ছে...",
"wsl.onboarding.step.distro": "ডিস্ট্রো নির্বাচন করুন",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL পরীক্ষা করা হচ্ছে",
"wsl.onboarding.checkingRuntime": "WSL পরীক্ষা করা হচ্ছে...",
"wsl.onboarding.restartRequired": "WSL ইনস্টল করা শেষ করার জন্য Windows-এর একটি রিস্টার্ট প্রয়োজন।",
"wsl.onboarding.ready": "WSL প্রস্তুত।",
"wsl.onboarding.required": "WSL চালিয়ে যেতে হবে।",
"wsl.onboarding.checkingDistros": "ডিস্ট্রো পরীক্ষা করা হচ্ছে",
"wsl.onboarding.installingDistro": "{{distro}} ইনস্টল করা হচ্ছে",
"wsl.onboarding.checkingDistro": "চেক করা হচ্ছে {{distro}}",
"wsl.onboarding.listingDistros": "ডিস্ট্রোদের তালিকা করা হচ্ছে",
"wsl.onboarding.checkingDistros": "ডিস্ট্রো পরীক্ষা করা হচ্ছে...",
"wsl.onboarding.installingDistro": "{{distro}} ইনস্টল করা হচ্ছে...",
"wsl.onboarding.checkingDistro": "চেক করা হচ্ছে {{distro}}...",
"wsl.onboarding.listingDistros": "ডিস্ট্রোদের তালিকা করা হচ্ছে...",
"wsl.onboarding.distroReady": "{{distro}} প্রস্তুত।",
"wsl.onboarding.distroNotInstalled": "{{distro}} এখনও ইনস্টল করা হয়নি৷",
"wsl.onboarding.openDistroOnce": "সেটআপ শেষ করতে একবার {{distro}} খুলুন।",
"wsl.onboarding.finishingDistro": "{{distro}} এর জন্য সেটআপ শেষ করা হচ্ছে।",
"wsl.onboarding.pickDistro": "একটি ডিস্ট্রো চয়ন করুন বা নীচে একটি ইনস্টল করুন৷",
"wsl.onboarding.checkingOpencode": "OpenCode পরীক্ষা করা হচ্ছে",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} এ OpenCode চেক করা হচ্ছে",
"wsl.onboarding.updatingOpencode": "আপডেট করা হচ্ছে OpenCode",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}-এ OpenCode আপডেট করা হচ্ছে",
"wsl.onboarding.checkingOpencode": "OpenCode পরীক্ষা করা হচ্ছে...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} এ OpenCode চেক করা হচ্ছে...",
"wsl.onboarding.updatingOpencode": "আপডেট করা হচ্ছে OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}-এ OpenCode আপডেট করা হচ্ছে...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}-এ OpenCode আপডেট করুন।",
"wsl.onboarding.updateOpencode": "OpenCode আপডেট করুন",
"wsl.onboarding.opencodeReadyIn": "OpenCode {{distro}}-এ প্রস্তুত।",
@@ -467,11 +467,11 @@ export const dict: Record<string, string> = {
"wsl.onboarding.installOpencode": "OpenCode ইনস্টল করুন",
"wsl.onboarding.chooseDistroFirst": "প্রথমে একটি ডিস্ট্রো চয়ন করুন।",
"wsl.onboarding.loadFailed": "WSL অবস্থা লোড করতে ব্যর্থ।",
"wsl.onboarding.loading": "লোড হচ্ছে",
"wsl.onboarding.loading": "লোড হচ্ছে...",
"wsl.onboarding.installedDistros": "ডিস্ট্রো ইনস্টল করা হয়েছে",
"wsl.onboarding.checkAgain": "আবার চেক করুন",
"wsl.onboarding.distroStatus.ready": "প্রস্তুত",
"wsl.onboarding.distroStatus.checking": "পরীক্ষা করা হচ্ছে",
"wsl.onboarding.distroStatus.checking": "পরীক্ষা করা হচ্ছে...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode ইনস্টল করা নেই",
"wsl.onboarding.distroStatus.missingTools": "অনুপস্থিত বাশ, কার্ল",
"wsl.onboarding.distroStatus.unsupported": "অসমর্থিত · WSL 2 ব্যবহার করুন",
@@ -490,7 +490,7 @@ export const dict: Record<string, string> = {
"wsl.onboarding.allDistrosAdded": "সমস্ত ইনস্টল করা ডিস্ট্রো ইতিমধ্যেই যোগ করা হয়েছে।",
"wsl.onboarding.noDistros": "কোন ডিস্ট্রো এখনও সনাক্ত করা হয়নি.",
"wsl.onboarding.install": "ইনস্টল করুন",
"wsl.onboarding.installing": "ইনস্টল করা হচ্ছে",
"wsl.onboarding.installing": "ইনস্টল করা হচ্ছে...",
"wsl.onboarding.installDistro": "ডিস্ট্রো ইনস্টল করুন",
"wsl.onboarding.searchDistros": "ডিস্ট্রো অনুসন্ধান করুন",
"wsl.onboarding.wsl2Required": "WSL 2 প্রয়োজন।",
@@ -502,7 +502,7 @@ export const dict: Record<string, string> = {
"wsl.onboarding.unknown": "অজানা",
"wsl.onboarding.desktopVersion": "ডেস্কটপ {{version}}",
"wsl.onboarding.versionMismatch": "ইনস্টল করা সংস্করণ ডেস্কটপ অ্যাপ সংস্করণের সাথে মেলে না।",
"wsl.onboarding.adding": "যোগ করা হচ্ছে",
"wsl.onboarding.adding": "যোগ করা হচ্ছে...",
"dialog.project.edit.title": "প্রকল্প সম্পাদনা করুন",
"dialog.project.edit.name": "নাম",
"dialog.project.edit.icon": "আইকন",
@@ -607,7 +607,7 @@ export const dict: Record<string, string> = {
"error.page.action.report": "রিপোর্ট ত্রুটি",
"error.page.action.reported": "ত্রুটি রিপোর্ট করা হয়েছে",
"error.page.action.exportLogs": "রপ্তানি লগ",
"error.page.action.checking": "পরীক্ষা করা হচ্ছে",
"error.page.action.checking": "পরীক্ষা করা হচ্ছে...",
"error.page.action.checkUpdates": "আপডেটের জন্য চেক করুন",
"error.page.action.updateTo": "{{version}}-এ আপডেট করুন",
"error.page.circular": "[সার্কুলার]",
@@ -685,13 +685,13 @@ export const dict: Record<string, string> = {
"session.review.filesChanged": "ফাইলগুলি পরিবর্তন করা হয়েছে {{count}}৷",
"session.review.change.one": "পরিবর্তন",
"session.review.change.other": "পরিবর্তন",
"session.review.loadingChanges": "পরিবর্তনগুলি লোড হচ্ছে",
"session.review.loadingChanges": "পরিবর্তনগুলি লোড হচ্ছে...",
"session.review.empty": "এই সেশনে এখনও কোন পরিবর্তন নেই",
"session.review.noVcs": "কোন Git সংস্করণ নিয়ন্ত্রণ সিস্টেম সনাক্ত করা যায়নি, পরিবর্তনগুলি প্রদর্শিত হয় না",
"session.review.noVcs.createGit.title": "একটি Git সংগ্রহস্থল তৈরি করুন",
"session.review.noVcs.createGit.description":
"এই প্রকল্পের পরিবর্তনগুলি ট্র্যাক করুন, পর্যালোচনা করুন এবং পূর্বাবস্থায় ফেরান৷",
"session.review.noVcs.createGit.actionLoading": "Git সংগ্রহস্থল তৈরি করা হচ্ছে",
"session.review.noVcs.createGit.actionLoading": "Git সংগ্রহস্থল তৈরি করা হচ্ছে...",
"session.review.noVcs.createGit.action": "Git সংগ্রহস্থল তৈরি করুন",
"session.review.noSnapshot": "কনফিগারেশনে স্ন্যাপশট ট্র্যাকিং অক্ষম করা হয়েছে, তাই সেশন পরিবর্তনগুলি অনুপলব্ধ৷",
"session.review.noChanges": "কোনো পরিবর্তন নেই",
@@ -702,9 +702,9 @@ export const dict: Record<string, string> = {
"session.files.empty": "কোনো ফাইল নেই",
"session.files.binaryContent": "বাইনারি ফাইল (সামগ্রী প্রদর্শন করা যাবে না)",
"session.messages.renderEarlier": "আগের বার্তা রেন্ডার করুন",
"session.messages.loadingEarlier": "আগের বার্তা লোড হচ্ছে",
"session.messages.loadingEarlier": "আগের বার্তা লোড হচ্ছে...",
"session.messages.loadEarlier": "আগের বার্তা লোড করুন",
"session.messages.loading": "বার্তা লোড হচ্ছে",
"session.messages.loading": "বার্তা লোড হচ্ছে...",
"session.messages.jumpToLatest": "সর্বশেষে ঝাঁপ দাও",
"session.context.addToContext": "প্রসঙ্গে {{selection}} যোগ করুন",
"session.todo.title": "টোডোস",
@@ -765,8 +765,8 @@ export const dict: Record<string, string> = {
"common.copied": "কপি করা হয়েছে",
"lsp.tooltip.none": "কোনো LSP সার্ভার নেই",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "প্রম্পট লোড হচ্ছে",
"terminal.loading": "টার্মিনাল লোড হচ্ছে",
"prompt.loading": "প্রম্পট লোড হচ্ছে...",
"terminal.loading": "টার্মিনাল লোড হচ্ছে...",
"terminal.title": "টার্মিনাল",
"terminal.title.numbered": "টার্মিনাল {{number}}",
"terminal.close": "টার্মিনাল বন্ধ করুন",
@@ -925,9 +925,9 @@ export const dict: Record<string, string> = {
"settings.updates.row.check.title": "আপডেটের জন্য চেক করুন",
"settings.updates.row.check.description": "আপডেটের জন্য ম্যানুয়ালি পরীক্ষা করুন এবং উপলব্ধ থাকলে ইনস্টল করুন",
"settings.updates.action.checkNow": "এখন পরীক্ষা করুন",
"settings.updates.action.checking": "পরীক্ষা করা হচ্ছে",
"settings.updates.action.downloading": "ডাউনলোড হচ্ছে",
"settings.updates.action.installing": "ইনস্টল করা হচ্ছে",
"settings.updates.action.checking": "পরীক্ষা করা হচ্ছে...",
"settings.updates.action.downloading": "ডাউনলোড হচ্ছে...",
"settings.updates.action.installing": "ইনস্টল করা হচ্ছে...",
"settings.updates.toast.latest.title": "আপনি আপ টু ডেট",
"settings.updates.toast.latest.description": "আপনি OpenCode এর সর্বশেষ সংস্করণ চালাচ্ছেন।",
"sound.option.none": "কোনোটিই নয়",
@@ -1074,7 +1074,7 @@ export const dict: Record<string, string> = {
"workspace.reset.success.title": "ওয়ার্কস্পেস রিসেট",
"workspace.reset.success.description": "ওয়ার্কস্পেস এখন ডিফল্ট শাখার সাথে মেলে।",
"workspace.error.stillPreparing": "ওয়ার্কস্পেস এখনও প্রস্তুত করা হয়",
"workspace.status.checking": "একত্রিত না হওয়া পরিবর্তনগুলি পরীক্ষা করা হচ্ছে",
"workspace.status.checking": "একত্রিত না হওয়া পরিবর্তনগুলি পরীক্ষা করা হচ্ছে...",
"workspace.status.error": "গিট স্ট্যাটাস যাচাই করতে অক্ষম।",
"workspace.status.clean": "কোনো একীভূত পরিবর্তন সনাক্ত করা যায়নি.",
"workspace.status.dirty": "এই ওয়ার্কস্পেসে একত্রিত না হওয়া পরিবর্তন শনাক্ত করা হয়েছে।",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Ir",
"desktop.menu.window": "Janela",
"desktop.menu.help": "Ajuda",
"desktop.menu.checkForUpdates": "Verificar atualizações",
"desktop.menu.checkForUpdates": "Verificar atualizações...",
"desktop.menu.settings": "Configurações",
"desktop.menu.reloadWebview": "Recarregar WebView",
"desktop.menu.restart": "Reiniciar",
"desktop.menu.exportLogs": "Exportar logs",
"desktop.menu.exportLogs": "Exportar logs...",
"desktop.menu.newSession": "Nova sessão",
"desktop.menu.openProject": "Abrir projeto",
"desktop.menu.openProject": "Abrir projeto...",
"desktop.menu.newWindow": "Nova janela",
"desktop.menu.closeWindow": "Fechar janela",
"desktop.menu.undo": "Desfazer",
@@ -221,8 +221,8 @@ export const dict = {
"provider.connect.method.apiKey": "Chave de API",
"provider.connect.method.browser": "Navegador",
"provider.connect.method.headless": "Sem interface gráfica",
"provider.connect.status.inProgress": "Autorização em andamento",
"provider.connect.status.waiting": "Aguardando autorização",
"provider.connect.status.inProgress": "Autorização em andamento...",
"provider.connect.status.waiting": "Aguardando autorização...",
"provider.connect.status.failed": "Autorização falhou: {{error}}",
"provider.connect.apiKey.description":
"Digite sua chave de API do {{provider}} para conectar sua conta e usar modelos do {{provider}} no OpenCode.",
@@ -315,7 +315,7 @@ export const dict = {
"common.goBack": "Voltar",
"common.goForward": "Avançar",
"common.loading": "Carregando",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Cancelar",
"common.clear": "Limpar",
"common.connect": "Conectar",
@@ -323,12 +323,12 @@ export const dict = {
"common.continue": "Continuar",
"common.submit": "Enviar",
"common.save": "Salvar",
"common.saving": "Salvando",
"common.saving": "Salvando...",
"common.default": "Padrão",
"common.attachment": "anexo",
"prompt.placeholder.shell": "Digite comando do shell {{example}}",
"prompt.placeholder.normal": 'Pergunte qualquer coisa "{{example}}"',
"prompt.placeholder.simple": "Pergunte qualquer coisa",
"prompt.placeholder.shell": "Digite comando do shell... {{example}}",
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
"prompt.placeholder.simple": "Pergunte qualquer coisa...",
"prompt.placeholder.summarizeComments": "Resumir comentários…",
"prompt.placeholder.summarizeComment": "Resumir comentário…",
"prompt.mode.shell": "Shell",
@@ -348,7 +348,7 @@ export const dict = {
"prompt.example.10": "Gerar documentação da API",
"prompt.example.11": "Otimizar consultas ao banco de dados",
"prompt.example.12": "Adicionar validação de entrada",
"prompt.example.13": "Criar um novo componente para",
"prompt.example.13": "Criar um novo componente para...",
"prompt.example.14": "Como faço o deploy deste projeto?",
"prompt.example.15": "Revisar meu código para boas práticas",
"prompt.example.16": "Adicionar tratamento de erros a esta função",
@@ -359,7 +359,7 @@ export const dict = {
"prompt.example.21": "Me ajude a escrever um script de migração",
"prompt.example.22": "Implementar cache para este endpoint",
"prompt.example.23": "Adicionar paginação a esta lista",
"prompt.example.24": "Criar um comando CLI para",
"prompt.example.24": "Criar um comando CLI para...",
"prompt.example.25": "Como funcionam as variáveis de ambiente aqui?",
"prompt.popover.emptyResults": "Nenhum resultado correspondente",
"prompt.popover.emptyCommands": "Nenhum comando correspondente",
@@ -418,7 +418,7 @@ export const dict = {
"dialog.server.add.url": "Endereço do servidor",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Não foi possível conectar ao servidor",
"dialog.server.add.checking": "Verificando",
"dialog.server.add.checking": "Verificando...",
"dialog.server.add.button": "Adicionar servidor",
"dialog.server.add.name": "Nome do servidor (opcional)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -442,26 +442,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Tentar iniciar novamente",
"wsl.server.updating": "Atualizando",
"wsl.server.updating": "Atualizando...",
"wsl.onboarding.step.distro": "Escolher distribuição",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Verificando o WSL",
"wsl.onboarding.checkingRuntime": "Verificando o WSL...",
"wsl.onboarding.restartRequired": "O Windows precisa ser reiniciado para concluir a instalação do WSL.",
"wsl.onboarding.ready": "O WSL está pronto.",
"wsl.onboarding.required": "O WSL é necessário para continuar.",
"wsl.onboarding.checkingDistros": "Verificando distribuições",
"wsl.onboarding.installingDistro": "Instalando {{distro}}",
"wsl.onboarding.checkingDistro": "Verificando {{distro}}",
"wsl.onboarding.listingDistros": "Listando distribuições",
"wsl.onboarding.checkingDistros": "Verificando distribuições...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Verificando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuições...",
"wsl.onboarding.distroReady": "{{distro}} está pronta.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ainda não está instalada.",
"wsl.onboarding.openDistroOnce": "Abra {{distro}} uma vez para concluir a configuração.",
"wsl.onboarding.finishingDistro": "Concluindo a configuração de {{distro}}.",
"wsl.onboarding.pickDistro": "Escolha uma distribuição ou instale uma abaixo.",
"wsl.onboarding.checkingOpencode": "Verificando o OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}",
"wsl.onboarding.updatingOpencode": "Atualizando o OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}",
"wsl.onboarding.checkingOpencode": "Verificando o OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}...",
"wsl.onboarding.updatingOpencode": "Atualizando o OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Atualize o OpenCode em {{distro}}.",
"wsl.onboarding.updateOpencode": "Atualizar o OpenCode",
"wsl.onboarding.opencodeReadyIn": "O OpenCode está pronto em {{distro}}.",
@@ -470,11 +470,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Instalar o OpenCode",
"wsl.onboarding.chooseDistroFirst": "Escolha primeiro uma distribuição.",
"wsl.onboarding.loadFailed": "Falha ao carregar o estado do WSL.",
"wsl.onboarding.loading": "Carregando",
"wsl.onboarding.loading": "Carregando...",
"wsl.onboarding.installedDistros": "Distribuições instaladas",
"wsl.onboarding.checkAgain": "Verificar novamente",
"wsl.onboarding.distroStatus.ready": "Pronta",
"wsl.onboarding.distroStatus.checking": "Verificando",
"wsl.onboarding.distroStatus.checking": "Verificando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode não instalado",
"wsl.onboarding.distroStatus.missingTools": "bash e curl ausentes",
"wsl.onboarding.distroStatus.unsupported": "Sem suporte · Use o WSL 2",
@@ -493,7 +493,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Todas as distribuições instaladas já foram adicionadas.",
"wsl.onboarding.noDistros": "Nenhuma distribuição detectada ainda.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribuição",
"wsl.onboarding.searchDistros": "Buscar distribuições",
"wsl.onboarding.wsl2Required": "O WSL 2 é necessário.",
@@ -505,7 +505,7 @@ export const dict = {
"wsl.onboarding.unknown": "desconhecido",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.",
"wsl.onboarding.adding": "Adicionando",
"wsl.onboarding.adding": "Adicionando...",
"dialog.project.edit.title": "Editar projeto",
"dialog.project.edit.name": "Nome",
"dialog.project.edit.icon": "Ícone",
@@ -610,7 +610,7 @@ export const dict = {
"error.page.action.report": "Reportar erro",
"error.page.action.reported": "Erro reportado",
"error.page.action.exportLogs": "Exportar logs",
"error.page.action.checking": "Verificando",
"error.page.action.checking": "Verificando...",
"error.page.action.checkUpdates": "Verificar atualizações",
"error.page.action.updateTo": "Atualizar para {{version}}",
"error.page.report.prefix": "Por favor, reporte este erro para a equipe do OpenCode",
@@ -681,7 +681,7 @@ export const dict = {
"session.review.filesChanged": "{{count}} arquivos alterados",
"session.review.change.one": "Alteração",
"session.review.change.other": "Alterações",
"session.review.loadingChanges": "Carregando alterações",
"session.review.loadingChanges": "Carregando alterações...",
"session.review.empty": "Nenhuma alteração nesta sessão ainda",
"session.review.noVcs": "Nenhum Sistema de Controle de Versão Git detectado, alterações não exibidas",
"session.review.noSnapshot":
@@ -694,9 +694,9 @@ export const dict = {
"session.files.empty": "Nenhum arquivo",
"session.files.binaryContent": "Arquivo binário (conteúdo não pode ser exibido)",
"session.messages.renderEarlier": "Renderizar mensagens anteriores",
"session.messages.loadingEarlier": "Carregando mensagens anteriores",
"session.messages.loadingEarlier": "Carregando mensagens anteriores...",
"session.messages.loadEarlier": "Carregar mensagens anteriores",
"session.messages.loading": "Carregando mensagens",
"session.messages.loading": "Carregando mensagens...",
"session.messages.jumpToLatest": "Ir para a mais recente",
"session.context.addToContext": "Adicionar {{selection}} ao contexto",
"session.todo.title": "Tarefas",
@@ -737,8 +737,8 @@ export const dict = {
"common.copied": "Copiado",
"lsp.tooltip.none": "Nenhum servidor LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Carregando prompt",
"terminal.loading": "Carregando terminal",
"prompt.loading": "Carregando prompt...",
"terminal.loading": "Carregando terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Fechar terminal",
@@ -853,9 +853,9 @@ export const dict = {
"settings.updates.row.check.title": "Verificar atualizações",
"settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver",
"settings.updates.action.checkNow": "Verificar agora",
"settings.updates.action.checking": "Verificando",
"settings.updates.action.downloading": "Baixando",
"settings.updates.action.installing": "Instalando",
"settings.updates.action.checking": "Verificando...",
"settings.updates.action.downloading": "Baixando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Você está atualizado",
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
"sound.option.none": "Nenhum",
@@ -1002,7 +1002,7 @@ export const dict = {
"workspace.reset.success.title": "Espaço de trabalho redefinido",
"workspace.reset.success.description": "Espaço de trabalho agora corresponde ao branch padrão.",
"workspace.error.stillPreparing": "O espaço de trabalho ainda está sendo preparado",
"workspace.status.checking": "Verificando alterações não mescladas",
"workspace.status.checking": "Verificando alterações não mescladas...",
"workspace.status.error": "Não foi possível verificar o status do git.",
"workspace.status.clean": "Nenhuma alteração não mesclada detectada.",
"workspace.status.dirty": "Alterações não mescladas detectadas neste espaço de trabalho.",
@@ -1032,12 +1032,12 @@ export const dict = {
"settings.providers.custom.description": "Adicionar um provedor compatível com OpenAI por meio da URL base.",
"app.server.unreachable": "Não foi possível conectar a {{server}}",
"app.server.retrying": "Tentando novamente automaticamente",
"app.server.retrying": "Tentando novamente automaticamente...",
"app.server.otherServers": "Outros servidores",
"dialog.server.add.passwordPlaceholder": "senha",
"session.review.noVcs.createGit.title": "Criar um repositório Git",
"session.review.noVcs.createGit.description": "Rastreie, revise e desfaça alterações neste projeto",
"session.review.noVcs.createGit.actionLoading": "Criando repositório Git",
"session.review.noVcs.createGit.actionLoading": "Criando repositório Git...",
"session.review.noVcs.createGit.action": "Criar repositório Git",
"session.todo.progress": "{{done}} de {{total}} tarefas concluídas",
"session.question.progress": "{{current}} de {{total}} perguntas",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Idi",
"desktop.menu.window": "Prozor",
"desktop.menu.help": "Pomoć",
"desktop.menu.checkForUpdates": "Provjeri ima li ažuriranja",
"desktop.menu.checkForUpdates": "Provjeri ima li ažuriranja...",
"desktop.menu.settings": "Postavke",
"desktop.menu.reloadWebview": "Ponovo učitaj Webview",
"desktop.menu.restart": "Ponovo pokreni",
"desktop.menu.exportLogs": "Izvezi zapisnike",
"desktop.menu.exportLogs": "Izvezi zapisnike...",
"desktop.menu.newSession": "Nova sesija",
"desktop.menu.openProject": "Otvori projekat",
"desktop.menu.openProject": "Otvori projekat...",
"desktop.menu.newWindow": "Novi prozor",
"desktop.menu.closeWindow": "Zatvori prozor",
"desktop.menu.undo": "Poništi",
@@ -233,8 +233,8 @@ export const dict = {
"provider.connect.method.apiKey": "API ključ",
"provider.connect.method.browser": "Preglednik",
"provider.connect.method.headless": "Bez grafičkog interfejsa",
"provider.connect.status.inProgress": "Autorizacija je u toku",
"provider.connect.status.waiting": "Čekanje na autorizaciju",
"provider.connect.status.inProgress": "Autorizacija je u toku...",
"provider.connect.status.waiting": "Čekanje na autorizaciju...",
"provider.connect.status.failed": "Autorizacija nije uspjela: {{error}}",
"provider.connect.apiKey.description":
"Unesi svoj {{provider}} API ključ da povežeš račun i koristiš {{provider}} modele u OpenCode-u.",
@@ -332,7 +332,7 @@ export const dict = {
"common.goBack": "Nazad",
"common.goForward": "Naprijed",
"common.loading": "Učitavanje",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Otkaži",
"common.clear": "Očisti",
"common.connect": "Poveži",
@@ -340,13 +340,13 @@ export const dict = {
"common.continue": "Nastavi",
"common.submit": "Pošalji",
"common.save": "Sačuvaj",
"common.saving": "Čuvanje",
"common.saving": "Čuvanje...",
"common.default": "Podrazumijevano",
"common.attachment": "prilog",
"prompt.placeholder.shell": "Unesi shell naredbu {{example}}",
"prompt.placeholder.normal": 'Pitaj bilo šta "{{example}}"',
"prompt.placeholder.simple": "Pitaj bilo šta",
"prompt.placeholder.shell": "Unesi shell naredbu... {{example}}",
"prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"',
"prompt.placeholder.simple": "Pitaj bilo šta...",
"prompt.placeholder.summarizeComments": "Sažmi komentare…",
"prompt.placeholder.summarizeComment": "Sažmi komentar…",
"prompt.mode.shell": "Shell",
@@ -367,7 +367,7 @@ export const dict = {
"prompt.example.10": "Generiši API dokumentaciju",
"prompt.example.11": "Optimizuj upite prema bazi podataka",
"prompt.example.12": "Dodaj validaciju ulaza",
"prompt.example.13": "Napravi novu komponentu za",
"prompt.example.13": "Napravi novu komponentu za...",
"prompt.example.14": "Kako da postavim ovaj projekat?",
"prompt.example.15": "Pregledaj moj kod prema najboljim praksama",
"prompt.example.16": "Dodaj obradu grešaka u ovu funkciju",
@@ -378,7 +378,7 @@ export const dict = {
"prompt.example.21": "Pomozi mi da napišem migracijsku skriptu",
"prompt.example.22": "Implementiraj keširanje za ovu krajnju tačku",
"prompt.example.23": "Dodaj paginaciju u ovu listu",
"prompt.example.24": "Napravi CLI komandu za",
"prompt.example.24": "Napravi CLI komandu za...",
"prompt.example.25": "Kako ovdje rade varijable okruženja?",
"prompt.popover.emptyResults": "Nema rezultata",
@@ -445,7 +445,7 @@ export const dict = {
"dialog.server.add.url": "URL servera",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Nije moguće povezati se na server",
"dialog.server.add.checking": "Provjera",
"dialog.server.add.checking": "Provjera...",
"dialog.server.add.button": "Dodaj server",
"dialog.server.add.name": "Ime servera (opcionalno)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -470,26 +470,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Ponovi pokretanje",
"wsl.server.updating": "Ažuriranje",
"wsl.server.updating": "Ažuriranje...",
"wsl.onboarding.step.distro": "Odaberi distribuciju",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Provjera WSL-a",
"wsl.onboarding.checkingRuntime": "Provjera WSL-a...",
"wsl.onboarding.restartRequired": "Potrebno je ponovo pokrenuti Windows da bi se završila instalacija WSL-a.",
"wsl.onboarding.ready": "WSL je spreman.",
"wsl.onboarding.required": "WSL je potreban za nastavak.",
"wsl.onboarding.checkingDistros": "Provjera distribucija",
"wsl.onboarding.installingDistro": "Instaliranje {{distro}}",
"wsl.onboarding.checkingDistro": "Provjera {{distro}}",
"wsl.onboarding.listingDistros": "Učitavanje liste distribucija",
"wsl.onboarding.checkingDistros": "Provjera distribucija...",
"wsl.onboarding.installingDistro": "Instaliranje {{distro}}...",
"wsl.onboarding.checkingDistro": "Provjera {{distro}}...",
"wsl.onboarding.listingDistros": "Učitavanje liste distribucija...",
"wsl.onboarding.distroReady": "Distribucija {{distro}} je spremna.",
"wsl.onboarding.distroNotInstalled": "Distribucija {{distro}} još nije instalirana.",
"wsl.onboarding.openDistroOnce": "Otvori {{distro}} jednom da završiš postavljanje.",
"wsl.onboarding.finishingDistro": "Završavanje postavljanja za {{distro}}.",
"wsl.onboarding.pickDistro": "Odaberi distribuciju ili instaliraj jednu ispod.",
"wsl.onboarding.checkingOpencode": "Provjera OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}",
"wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}",
"wsl.onboarding.checkingOpencode": "Provjera OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Ažuriraj OpenCode u distribuciji {{distro}}.",
"wsl.onboarding.updateOpencode": "Ažuriraj OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode je spreman u distribuciji {{distro}}.",
@@ -498,11 +498,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Instaliraj OpenCode",
"wsl.onboarding.chooseDistroFirst": "Prvo odaberi distribuciju.",
"wsl.onboarding.loadFailed": "Nije uspjelo učitavanje WSL stanja.",
"wsl.onboarding.loading": "Učitavanje",
"wsl.onboarding.loading": "Učitavanje...",
"wsl.onboarding.installedDistros": "Instalirane distribucije",
"wsl.onboarding.checkAgain": "Provjeri ponovo",
"wsl.onboarding.distroStatus.ready": "Spremno",
"wsl.onboarding.distroStatus.checking": "Provjera",
"wsl.onboarding.distroStatus.checking": "Provjera...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nije instaliran",
"wsl.onboarding.distroStatus.missingTools": "Nedostaju bash i curl",
"wsl.onboarding.distroStatus.unsupported": "Nije podržano · Koristi WSL 2",
@@ -521,7 +521,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Sve instalirane distribucije su već dodane.",
"wsl.onboarding.noDistros": "Još nema otkrivenih distribucija.",
"wsl.onboarding.install": "Instaliraj",
"wsl.onboarding.installing": "Instaliranje",
"wsl.onboarding.installing": "Instaliranje...",
"wsl.onboarding.installDistro": "Instaliraj distribuciju",
"wsl.onboarding.searchDistros": "Pretraži distribucije",
"wsl.onboarding.wsl2Required": "Potreban je WSL 2.",
@@ -533,7 +533,7 @@ export const dict = {
"wsl.onboarding.unknown": "nepoznato",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.",
"wsl.onboarding.adding": "Dodavanje",
"wsl.onboarding.adding": "Dodavanje...",
"dialog.project.edit.title": "Uredi projekat",
"dialog.project.edit.name": "Naziv",
@@ -656,7 +656,7 @@ export const dict = {
"error.page.action.report": "Prijavi grešku",
"error.page.action.reported": "Greška prijavljena",
"error.page.action.exportLogs": "Izvezi logove",
"error.page.action.checking": "Provjera",
"error.page.action.checking": "Provjera...",
"error.page.action.checkUpdates": "Provjeri ažuriranja",
"error.page.action.updateTo": "Ažuriraj na {{version}}",
"error.page.report.prefix": "Molimo prijavi ovu grešku OpenCode timu",
@@ -733,7 +733,7 @@ export const dict = {
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
"session.review.change.one": "Izmjena",
"session.review.change.other": "Izmjene",
"session.review.loadingChanges": "Učitavanje izmjena",
"session.review.loadingChanges": "Učitavanje izmjena...",
"session.review.empty": "Još nema izmjena u ovoj sesiji",
"session.review.noVcs": "Nije otkriven Git sistem za kontrolu verzija, pa se promjene ne prikazuju",
"session.review.noSnapshot":
@@ -748,9 +748,9 @@ export const dict = {
"session.files.binaryContent": "Binarna datoteka (sadržaj se ne može prikazati)",
"session.messages.renderEarlier": "Prikaži ranije poruke",
"session.messages.loadingEarlier": "Učitavanje ranijih poruka",
"session.messages.loadingEarlier": "Učitavanje ranijih poruka...",
"session.messages.loadEarlier": "Učitaj ranije poruke",
"session.messages.loading": "Učitavanje poruka",
"session.messages.loading": "Učitavanje poruka...",
"session.messages.jumpToLatest": "Idi na najnovije",
"session.context.addToContext": "Dodaj {{selection}} u kontekst",
@@ -798,8 +798,8 @@ export const dict = {
"lsp.tooltip.none": "Nema LSP servera",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Učitavanje upita",
"terminal.loading": "Učitavanje terminala",
"prompt.loading": "Učitavanje upita...",
"terminal.loading": "Učitavanje terminala...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Zatvori terminal",
@@ -922,9 +922,9 @@ export const dict = {
"settings.updates.row.check.title": "Provjeri ažuriranja",
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
"settings.updates.action.checkNow": "Provjeri sada",
"settings.updates.action.checking": "Provjera",
"settings.updates.action.downloading": "Preuzimanje",
"settings.updates.action.installing": "Instaliranje",
"settings.updates.action.checking": "Provjera...",
"settings.updates.action.downloading": "Preuzimanje...",
"settings.updates.action.installing": "Instaliranje...",
"settings.updates.toast.latest.title": "Sve je ažurno",
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
"sound.option.none": "Nijedan",
@@ -1080,7 +1080,7 @@ export const dict = {
"workspace.reset.success.title": "Radni prostor vraćen na početno stanje",
"workspace.reset.success.description": "Radni prostor sada odgovara podrazumijevanoj grani.",
"workspace.error.stillPreparing": "Radni prostor se još priprema",
"workspace.status.checking": "Provjera promjena koje nisu commitovane",
"workspace.status.checking": "Provjera promjena koje nisu commitovane...",
"workspace.status.error": "Nije moguće provjeriti git status.",
"workspace.status.clean": "Nisu pronađene promjene koje nisu commitovane.",
"workspace.status.dirty": "Pronađene su promjene koje nisu commitovane u ovom radnom prostoru.",
@@ -1110,12 +1110,12 @@ export const dict = {
"settings.providers.custom.description": "Dodaj provajdera kompatibilnog s OpenAI putem baznog URL-a.",
"app.server.unreachable": "Nije moguće pristupiti {{server}}",
"app.server.retrying": "Automatski ponovni pokušaj",
"app.server.retrying": "Automatski ponovni pokušaj...",
"app.server.otherServers": "Drugi serveri",
"dialog.server.add.passwordPlaceholder": "lozinka",
"session.review.noVcs.createGit.title": "Kreiraj Git repozitorij",
"session.review.noVcs.createGit.description": "Prati, pregledaj i poništi promjene u ovom projektu",
"session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija",
"session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija...",
"session.review.noVcs.createGit.action": "Kreiraj Git repozitorij",
"session.todo.progress": "{{done}} od {{total}} zadataka završeno",
"session.question.progress": "{{current}} od {{total}} pitanja",
+41 -41
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Vés",
"desktop.menu.window": "Finestra",
"desktop.menu.help": "Ajuda",
"desktop.menu.checkForUpdates": "Comproveu si hi ha actualitzacions",
"desktop.menu.checkForUpdates": "Comproveu si hi ha actualitzacions...",
"desktop.menu.settings": "Configuració",
"desktop.menu.reloadWebview": "Torna a carregar Webview",
"desktop.menu.restart": "Reinicieu",
"desktop.menu.exportLogs": "Exporta registres",
"desktop.menu.exportLogs": "Exporta registres...",
"desktop.menu.newSession": "Nova sessió",
"desktop.menu.openProject": "Projecte obert",
"desktop.menu.openProject": "Projecte obert...",
"desktop.menu.newWindow": "Finestra nova",
"desktop.menu.closeWindow": "Tanca la finestra",
"desktop.menu.undo": "Desfer",
@@ -214,8 +214,8 @@ export const dict = {
"provider.connect.method.apiKey": "tecla API.",
"provider.connect.method.browser": "Navegador",
"provider.connect.method.headless": "Sense cap",
"provider.connect.status.inProgress": "Autorització en curs",
"provider.connect.status.waiting": "Esperant l'autorització",
"provider.connect.status.inProgress": "Autorització en curs...",
"provider.connect.status.waiting": "Esperant l'autorització...",
"provider.connect.status.failed": "L'autorització ha fallat: {{error}}",
"provider.connect.apiKey.description":
"Introduïu la clau API de {{provider}} per connectar el compte i utilitzar els models de {{provider}} a OpenCode.",
@@ -309,7 +309,7 @@ export const dict = {
"common.goBack": "Navega enrere",
"common.goForward": "Navega cap endavant",
"common.loading": "Carregant",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel·la",
"common.clear": "Clar",
"common.open": "Obert",
@@ -318,14 +318,14 @@ export const dict = {
"common.continue": "Continua",
"common.submit": "Envia",
"common.save": "Desa",
"common.saving": "S'està desant",
"common.saving": "S'està desant...",
"common.default": "Per defecte",
"common.attachment": "adjunt",
"prompt.placeholder.shell": "Introduïu una ordre de l'intèrpret d'ordres {{example}}",
"prompt.placeholder.normal": 'Pregunta qualsevol cosa "{{example}}"',
"prompt.placeholder.simple": "Pregunta qualsevol cosa",
"prompt.placeholder.summarizeComments": "Resum dels comentaris",
"prompt.placeholder.summarizeComment": "Resumeix el comentari",
"prompt.placeholder.shell": "Introduïu una ordre de l'intèrpret d'ordres... {{example}}",
"prompt.placeholder.normal": 'Pregunta qualsevol cosa... "{{example}}"',
"prompt.placeholder.simple": "Pregunta qualsevol cosa...",
"prompt.placeholder.summarizeComments": "Resum dels comentaris...",
"prompt.placeholder.summarizeComment": "Resumeix el comentari...",
"prompt.mode.shell": "Shell",
"prompt.mode.normal": "Indicació",
"prompt.mode.shell.exit": "esc per sortir",
@@ -343,7 +343,7 @@ export const dict = {
"prompt.example.10": "Genera API documentació",
"prompt.example.11": "Optimitzar les consultes a la base de dades",
"prompt.example.12": "Afegiu la validació d'entrada",
"prompt.example.13": "Crea un component nou per",
"prompt.example.13": "Crea un component nou per...",
"prompt.example.14": "Com implemento aquest projecte?",
"prompt.example.15": "Revisa el meu codi per conèixer les pràctiques recomanades",
"prompt.example.16": "Afegiu la gestió d'errors a aquesta funció",
@@ -354,7 +354,7 @@ export const dict = {
"prompt.example.21": "Ajudeu-me a escriure un script de migració",
"prompt.example.22": "Implementeu la memòria cau per a aquest punt final",
"prompt.example.23": "Afegeix la paginació a aquesta llista",
"prompt.example.24": "Creeu una ordre CLI per",
"prompt.example.24": "Creeu una ordre CLI per...",
"prompt.example.25": "Com funcionen les variables d'entorn aquí?",
"prompt.popover.emptyResults": "No hi ha resultats coincidents",
"prompt.popover.emptyCommands": "No hi ha ordres coincidents",
@@ -406,7 +406,7 @@ export const dict = {
"dialog.directory.parent": "Pare",
"dialog.directory.readError": "No es pot llegir aquesta carpeta",
"app.server.unreachable": "No s'ha pogut arribar a {{server}}",
"app.server.retrying": "S'està tornant a provar automàticament",
"app.server.retrying": "S'està tornant a provar automàticament...",
"app.server.otherServers": "Altres servidors",
"dialog.server.title": "Servidors",
"dialog.server.description": "Canvia a quin OpenCode servidor es connecta aquesta aplicació.",
@@ -416,7 +416,7 @@ export const dict = {
"dialog.server.add.url": "Adreça del servidor",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "No s'ha pogut connectar al servidor",
"dialog.server.add.checking": "S'està comprovant",
"dialog.server.add.checking": "S'està comprovant...",
"dialog.server.add.button": "Afegeix servidor",
"dialog.server.add.name": "Nom del servidor (opcional)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -441,26 +441,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL servidor",
"wsl.server.retryStart": "Torna a intentar començar",
"wsl.server.updating": "S'està actualitzant",
"wsl.server.updating": "S'està actualitzant...",
"wsl.onboarding.step.distro": "Trieu la distribució",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "S'està comprovant WSL",
"wsl.onboarding.checkingRuntime": "S'està comprovant WSL...",
"wsl.onboarding.restartRequired": "Windows necessita un reinici per acabar la instal·lació WSL.",
"wsl.onboarding.ready": "WSL està llest.",
"wsl.onboarding.required": "WSL és necessari per continuar.",
"wsl.onboarding.checkingDistros": "S'estan comprovant les distribucions",
"wsl.onboarding.installingDistro": "S'està instal·lant {{distro}}",
"wsl.onboarding.checkingDistro": "S'està comprovant {{distro}}",
"wsl.onboarding.listingDistros": "Llistat de distribucions",
"wsl.onboarding.checkingDistros": "S'estan comprovant les distribucions...",
"wsl.onboarding.installingDistro": "S'està instal·lant {{distro}}...",
"wsl.onboarding.checkingDistro": "S'està comprovant {{distro}}...",
"wsl.onboarding.listingDistros": "Llistat de distribucions...",
"wsl.onboarding.distroReady": "{{distro}} està llest.",
"wsl.onboarding.distroNotInstalled": "{{distro}} encara no està instal·lat.",
"wsl.onboarding.openDistroOnce": "Obriu {{distro}} una vegada per acabar la configuració.",
"wsl.onboarding.finishingDistro": "S'està acabant la configuració de {{distro}}.",
"wsl.onboarding.pickDistro": "Trieu una distribució o instal·leu-ne una a continuació.",
"wsl.onboarding.checkingOpencode": "S'està comprovant OpenCode",
"wsl.onboarding.checkingOpencodeIn": "S'està registrant OpenCode a {{distro}}",
"wsl.onboarding.updatingOpencode": "S'està actualitzant OpenCode",
"wsl.onboarding.updatingOpencodeIn": "S'està actualitzant OpenCode a {{distro}}",
"wsl.onboarding.checkingOpencode": "S'està comprovant OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "S'està registrant OpenCode a {{distro}}...",
"wsl.onboarding.updatingOpencode": "S'està actualitzant OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "S'està actualitzant OpenCode a {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Actualitza OpenCode a {{distro}}.",
"wsl.onboarding.updateOpencode": "Actualització OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode està llest a {{distro}}.",
@@ -469,11 +469,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Instal·la OpenCode",
"wsl.onboarding.chooseDistroFirst": "Trieu primer una distribució.",
"wsl.onboarding.loadFailed": "No s'ha pogut carregar l'estat WSL.",
"wsl.onboarding.loading": "Carregant",
"wsl.onboarding.loading": "Carregant...",
"wsl.onboarding.installedDistros": "Distribucions instal·lades",
"wsl.onboarding.checkAgain": "Comproveu de nou",
"wsl.onboarding.distroStatus.ready": "A punt",
"wsl.onboarding.distroStatus.checking": "S'està comprovant",
"wsl.onboarding.distroStatus.checking": "S'està comprovant...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no instal·lat",
"wsl.onboarding.distroStatus.missingTools": "Falta bash, curl",
"wsl.onboarding.distroStatus.unsupported": "No s'admet · Utilitzeu WSL 2",
@@ -492,7 +492,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Totes les distribucions instal·lades ja s'han afegit.",
"wsl.onboarding.noDistros": "Encara no s'ha detectat cap distribució.",
"wsl.onboarding.install": "Instal·lar",
"wsl.onboarding.installing": "S'està instal·lant",
"wsl.onboarding.installing": "S'està instal·lant...",
"wsl.onboarding.installDistro": "Instal·leu la distribució",
"wsl.onboarding.searchDistros": "Cerca distribucions",
"wsl.onboarding.wsl2Required": "WSL Es requereix 2.",
@@ -504,7 +504,7 @@ export const dict = {
"wsl.onboarding.unknown": "desconegut",
"wsl.onboarding.desktopVersion": "escriptori {{version}}",
"wsl.onboarding.versionMismatch": "La versió instal·lada no coincideix amb la versió de l'aplicació d'escriptori.",
"wsl.onboarding.adding": "S'està afegint",
"wsl.onboarding.adding": "S'està afegint...",
"dialog.project.edit.title": "Edita el projecte",
"dialog.project.edit.name": "Nom",
"dialog.project.edit.icon": "Icona",
@@ -610,7 +610,7 @@ export const dict = {
"error.page.action.report": "Informa d'un error",
"error.page.action.reported": "S'ha informat d'un error",
"error.page.action.exportLogs": "Exporta registres",
"error.page.action.checking": "S'està comprovant",
"error.page.action.checking": "S'està comprovant...",
"error.page.action.checkUpdates": "Comproveu si hi ha actualitzacions",
"error.page.action.updateTo": "Actualitza a {{version}}",
"error.page.circular": "[Circular]",
@@ -690,12 +690,12 @@ export const dict = {
"session.review.filesChanged": "Fitxers canviats {{count}}",
"session.review.change.one": "Canviar",
"session.review.change.other": "Canvis",
"session.review.loadingChanges": "S'estan carregant els canvis",
"session.review.loadingChanges": "S'estan carregant els canvis...",
"session.review.empty": "Encara no hi ha canvis en aquesta sessió",
"session.review.noVcs": "No Git S'ha detectat el sistema de control de versions, els canvis no es mostren",
"session.review.noVcs.createGit.title": "Creeu un repositori Git.",
"session.review.noVcs.createGit.description": "Feu un seguiment, reviseu i desfer els canvis en aquest projecte",
"session.review.noVcs.createGit.actionLoading": "S'està creant un repositori Git",
"session.review.noVcs.createGit.actionLoading": "S'està creant un repositori Git...",
"session.review.noVcs.createGit.action": "Crea un repositori Git",
"session.review.noSnapshot":
"El seguiment de la instantània està desactivat a la configuració, de manera que els canvis de sessió no estan disponibles",
@@ -707,9 +707,9 @@ export const dict = {
"session.files.empty": "No hi ha fitxers",
"session.files.binaryContent": "Fitxer binari (el contingut no es pot mostrar)",
"session.messages.renderEarlier": "Mostra missatges anteriors",
"session.messages.loadingEarlier": "S'estan carregant missatges anteriors",
"session.messages.loadingEarlier": "S'estan carregant missatges anteriors...",
"session.messages.loadEarlier": "Carregueu missatges anteriors",
"session.messages.loading": "S'estan carregant els missatges",
"session.messages.loading": "S'estan carregant els missatges...",
"session.messages.jumpToLatest": "Ves a l'últim",
"session.context.addToContext": "Afegeix {{selection}} al context",
"session.todo.title": "Tots",
@@ -771,8 +771,8 @@ export const dict = {
"common.copied": "Copiat",
"lsp.tooltip.none": "No hi ha LSP servidors",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "S'està carregant el missatge",
"terminal.loading": "S'està carregant el terminal",
"prompt.loading": "S'està carregant el missatge...",
"terminal.loading": "S'està carregant el terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Tanca la terminal",
@@ -939,9 +939,9 @@ export const dict = {
"settings.updates.row.check.description":
"Comproveu manualment si hi ha actualitzacions i instal·leu si està disponible",
"settings.updates.action.checkNow": "Comproveu ara",
"settings.updates.action.checking": "S'està comprovant",
"settings.updates.action.downloading": "S'està baixant",
"settings.updates.action.installing": "S'està instal·lant",
"settings.updates.action.checking": "S'està comprovant...",
"settings.updates.action.downloading": "S'està baixant...",
"settings.updates.action.installing": "S'està instal·lant...",
"settings.updates.toast.latest.title": "Estàs al dia",
"settings.updates.toast.latest.description": "Esteu executant la darrera versió de OpenCode.",
"sound.option.none": "Cap",
@@ -1090,7 +1090,7 @@ export const dict = {
"workspace.reset.success.title": "Restabliment de l'espai de treball",
"workspace.reset.success.description": "L'espai de treball ara coincideix amb la branca predeterminada.",
"workspace.error.stillPreparing": "L'espai de treball encara s'està preparant",
"workspace.status.checking": "S'estan comprovant els canvis no combinats",
"workspace.status.checking": "S'estan comprovant els canvis no combinats...",
"workspace.status.error": "No es pot verificar l'estat de git.",
"workspace.status.clean": "No s'han detectat canvis no combinats.",
"workspace.status.dirty": "S'han detectat canvis no combinats en aquest espai de treball.",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Jdi",
"desktop.menu.window": "okno",
"desktop.menu.help": "Pomoc",
"desktop.menu.checkForUpdates": "Zkontrolovat aktualizace",
"desktop.menu.checkForUpdates": "Zkontrolovat aktualizace...",
"desktop.menu.settings": "Nastavení",
"desktop.menu.reloadWebview": "Znovu načíst Webview",
"desktop.menu.restart": "Restartujte",
"desktop.menu.exportLogs": "Exportovat protokoly",
"desktop.menu.exportLogs": "Exportovat protokoly...",
"desktop.menu.newSession": "Nová relace",
"desktop.menu.openProject": "Otevřít projekt",
"desktop.menu.openProject": "Otevřít projekt...",
"desktop.menu.newWindow": "Nové okno",
"desktop.menu.closeWindow": "Zavřít okno",
"desktop.menu.undo": "Vrátit zpět",
@@ -212,8 +212,8 @@ export const dict = {
"provider.connect.method.apiKey": "API klíč",
"provider.connect.method.browser": "Prohlížeč",
"provider.connect.method.headless": "Bezhlavě",
"provider.connect.status.inProgress": "Probíhá autorizace",
"provider.connect.status.waiting": "Čekání na autorizaci",
"provider.connect.status.inProgress": "Probíhá autorizace...",
"provider.connect.status.waiting": "Čekání na autorizaci...",
"provider.connect.status.failed": "Autorizace se nezdařila: {{error}}",
"provider.connect.apiKey.description":
"Zadejte klíč API služby {{provider}}, připojte účet a používejte modely {{provider}} v OpenCode.",
@@ -307,7 +307,7 @@ export const dict = {
"common.goBack": "Přejít zpět",
"common.goForward": "Přejděte vpřed",
"common.loading": "Načítání",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Zrušit",
"common.clear": "Jasný",
"common.open": "Otevřít",
@@ -316,12 +316,12 @@ export const dict = {
"common.continue": "Pokračujte",
"common.submit": "Odeslat",
"common.save": "Uložit",
"common.saving": "Ukládání",
"common.saving": "Ukládání...",
"common.default": "Výchozí",
"common.attachment": "příloha",
"prompt.placeholder.shell": "Zadejte příkaz shellu {{example}}",
"prompt.placeholder.normal": 'Zeptejte se na cokoliv "{{example}}"',
"prompt.placeholder.simple": "Zeptejte se na cokoliv",
"prompt.placeholder.shell": "Zadejte příkaz shellu... {{example}}",
"prompt.placeholder.normal": 'Zeptejte se na cokoliv... "{{example}}"',
"prompt.placeholder.simple": "Zeptejte se na cokoliv...",
"prompt.placeholder.summarizeComments": "Shrnout komentáře…",
"prompt.placeholder.summarizeComment": "Shrnout komentář…",
"prompt.mode.shell": "Shell",
@@ -341,7 +341,7 @@ export const dict = {
"prompt.example.10": "Vygenerujte dokumentaci API",
"prompt.example.11": "Optimalizujte databázové dotazy",
"prompt.example.12": "Přidejte ověření vstupu",
"prompt.example.13": "Vytvořit novou komponentu pro",
"prompt.example.13": "Vytvořit novou komponentu pro...",
"prompt.example.14": "Jak nasadím tento projekt?",
"prompt.example.15": "Osvědčené postupy naleznete v mém kódu",
"prompt.example.16": "Přidejte k této funkci zpracování chyb",
@@ -352,7 +352,7 @@ export const dict = {
"prompt.example.21": "Pomozte mi napsat migrační skript",
"prompt.example.22": "Implementujte ukládání do mezipaměti pro tento koncový bod",
"prompt.example.23": "Přidejte stránkování do tohoto seznamu",
"prompt.example.24": "Vytvořte příkaz CLI pro",
"prompt.example.24": "Vytvořte příkaz CLI pro...",
"prompt.example.25": "Jak zde fungují proměnné prostředí?",
"prompt.popover.emptyResults": "Žádné odpovídající výsledky",
"prompt.popover.emptyCommands": "Žádné odpovídající příkazy",
@@ -404,7 +404,7 @@ export const dict = {
"dialog.directory.parent": "Rodič",
"dialog.directory.readError": "Tuto složku nelze přečíst",
"app.server.unreachable": "Nelze dosáhnout {{server}}",
"app.server.retrying": "Automaticky opakování",
"app.server.retrying": "Automaticky opakování...",
"app.server.otherServers": "Jiné servery",
"dialog.server.title": "servery",
"dialog.server.description": "Přepněte, ke kterému OpenCode serveru se tato aplikace připojuje.",
@@ -414,7 +414,7 @@ export const dict = {
"dialog.server.add.url": "Adresa serveru",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Nelze se připojit k serveru",
"dialog.server.add.checking": "Kontrola",
"dialog.server.add.checking": "Kontrola...",
"dialog.server.add.button": "Přidat server",
"dialog.server.add.name": "Název serveru (volitelné)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -439,26 +439,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Zkuste spustit znovu",
"wsl.server.updating": "Aktualizace",
"wsl.server.updating": "Aktualizace...",
"wsl.onboarding.step.distro": "Vyberte distro",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Kontrola WSL",
"wsl.onboarding.checkingRuntime": "Kontrola WSL...",
"wsl.onboarding.restartRequired": "Windows vyžaduje restart k dokončení instalace WSL.",
"wsl.onboarding.ready": "WSL je připraven.",
"wsl.onboarding.required": "Pro pokračování je vyžadováno WSL.",
"wsl.onboarding.checkingDistros": "Kontrola distribucí",
"wsl.onboarding.installingDistro": "Instalace {{distro}}",
"wsl.onboarding.checkingDistro": "Kontrola {{distro}}",
"wsl.onboarding.listingDistros": "Výpis distribucí",
"wsl.onboarding.checkingDistros": "Kontrola distribucí...",
"wsl.onboarding.installingDistro": "Instalace {{distro}}...",
"wsl.onboarding.checkingDistro": "Kontrola {{distro}}...",
"wsl.onboarding.listingDistros": "Výpis distribucí...",
"wsl.onboarding.distroReady": "{{distro}} je připraven.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ještě není nainstalováno.",
"wsl.onboarding.openDistroOnce": "Pro dokončení nastavení jednou otevřete {{distro}}.",
"wsl.onboarding.finishingDistro": "Dokončování nastavení pro {{distro}}.",
"wsl.onboarding.pickDistro": "Vyberte distribuci nebo ji nainstalujte níže.",
"wsl.onboarding.checkingOpencode": "Kontrola OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Kontrola OpenCode v {{distro}}",
"wsl.onboarding.updatingOpencode": "Aktualizace OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Aktualizace OpenCode v {{distro}}",
"wsl.onboarding.checkingOpencode": "Kontrola OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Kontrola OpenCode v {{distro}}...",
"wsl.onboarding.updatingOpencode": "Aktualizace OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Aktualizace OpenCode v {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Aktualizace OpenCode v {{distro}}.",
"wsl.onboarding.updateOpencode": "Aktualizovat OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode je připraveno v {{distro}}.",
@@ -467,11 +467,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Instalovat OpenCode",
"wsl.onboarding.chooseDistroFirst": "Nejprve si vyberte distro.",
"wsl.onboarding.loadFailed": "Nepodařilo se načíst stav WSL.",
"wsl.onboarding.loading": "Načítání",
"wsl.onboarding.loading": "Načítání...",
"wsl.onboarding.installedDistros": "Nainstalované distribuce",
"wsl.onboarding.checkAgain": "Zkontrolujte znovu",
"wsl.onboarding.distroStatus.ready": "Připraveno",
"wsl.onboarding.distroStatus.checking": "Kontrola",
"wsl.onboarding.distroStatus.checking": "Kontrola...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode není nainstalováno",
"wsl.onboarding.distroStatus.missingTools": "Chybí bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Nepodporováno · Použijte WSL 2",
@@ -490,7 +490,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Všechny nainstalované distribuce jsou již přidány.",
"wsl.onboarding.noDistros": "Dosud nebyla zjištěna žádná distribuce.",
"wsl.onboarding.install": "Instalovat",
"wsl.onboarding.installing": "Instalace",
"wsl.onboarding.installing": "Instalace...",
"wsl.onboarding.installDistro": "Nainstalujte distro",
"wsl.onboarding.searchDistros": "Hledat distribuce",
"wsl.onboarding.wsl2Required": "WSL 2 je vyžadováno.",
@@ -502,7 +502,7 @@ export const dict = {
"wsl.onboarding.unknown": "neznámý",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Nainstalovaná verze neodpovídá verzi aplikace pro stolní počítače.",
"wsl.onboarding.adding": "Přidávání",
"wsl.onboarding.adding": "Přidávání...",
"dialog.project.edit.title": "Upravit projekt",
"dialog.project.edit.name": "Jméno",
"dialog.project.edit.icon": "ikona",
@@ -608,7 +608,7 @@ export const dict = {
"error.page.action.report": "Nahlásit chybu",
"error.page.action.reported": "Nahlášena chyba",
"error.page.action.exportLogs": "Exportovat protokoly",
"error.page.action.checking": "Kontrola",
"error.page.action.checking": "Kontrola...",
"error.page.action.checkUpdates": "Zkontrolujte aktualizace",
"error.page.action.updateTo": "Aktualizovat na {{version}}",
"error.page.circular": "[Kruhový]",
@@ -686,12 +686,12 @@ export const dict = {
"session.review.filesChanged": "Soubory změněny {{count}}",
"session.review.change.one": "Změna",
"session.review.change.other": "Změny",
"session.review.loadingChanges": "Načítání změn",
"session.review.loadingChanges": "Načítání změn...",
"session.review.empty": "V této relaci zatím žádné změny",
"session.review.noVcs": "Ne Git Zjištěn systém správy verzí, změny se nezobrazují",
"session.review.noVcs.createGit.title": "Vytvořte úložiště Git",
"session.review.noVcs.createGit.description": "Sledujte, kontrolujte a vracejte změny v tomto projektu",
"session.review.noVcs.createGit.actionLoading": "Vytváření Git úložiště",
"session.review.noVcs.createGit.actionLoading": "Vytváření Git úložiště...",
"session.review.noVcs.createGit.action": "Vytvořit Git úložiště",
"session.review.noSnapshot": "Sledování snímků je v konfiguraci zakázáno, takže změny relace nejsou dostupné",
"session.review.noChanges": "Žádné změny",
@@ -702,9 +702,9 @@ export const dict = {
"session.files.empty": "Žádné soubory",
"session.files.binaryContent": "Binární soubor (obsah nelze zobrazit)",
"session.messages.renderEarlier": "Vykreslit dřívější zprávy",
"session.messages.loadingEarlier": "Načítání dřívějších zpráv",
"session.messages.loadingEarlier": "Načítání dřívějších zpráv...",
"session.messages.loadEarlier": "Načíst dřívější zprávy",
"session.messages.loading": "Načítání zpráv",
"session.messages.loading": "Načítání zpráv...",
"session.messages.jumpToLatest": "Přejít na nejnovější",
"session.context.addToContext": "Přidejte {{selection}} do kontextu",
"session.todo.title": "Úkoly",
@@ -767,8 +767,8 @@ export const dict = {
"common.copied": "Zkopírováno",
"lsp.tooltip.none": "Žádné LSP servery",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Načítání výzvy",
"terminal.loading": "Načítání terminálu",
"prompt.loading": "Načítání výzvy...",
"terminal.loading": "Načítání terminálu...",
"terminal.title": "Terminál",
"terminal.title.numbered": "Terminál {{number}}",
"terminal.close": "Zavřete terminál",
@@ -932,9 +932,9 @@ export const dict = {
"settings.updates.row.check.title": "Zkontrolujte aktualizace",
"settings.updates.row.check.description": "Ručně zkontrolujte aktualizace a nainstalujte je, pokud jsou k dispozici",
"settings.updates.action.checkNow": "Zkontrolujte nyní",
"settings.updates.action.checking": "Kontrola",
"settings.updates.action.downloading": "Stahování",
"settings.updates.action.installing": "Instalace",
"settings.updates.action.checking": "Kontrola...",
"settings.updates.action.downloading": "Stahování...",
"settings.updates.action.installing": "Instalace...",
"settings.updates.toast.latest.title": "Jste aktuální",
"settings.updates.toast.latest.description": "Používáte nejnovější verzi OpenCode.",
"sound.option.none": "žádný",
@@ -1082,7 +1082,7 @@ export const dict = {
"workspace.reset.success.title": "Obnovení pracovního prostoru",
"workspace.reset.success.description": "Pracovní prostor nyní odpovídá výchozí větvi.",
"workspace.error.stillPreparing": "Pracovní plocha se stále připravuje",
"workspace.status.checking": "Kontrola nesloučených změn",
"workspace.status.checking": "Kontrola nesloučených změn...",
"workspace.status.error": "Nelze ověřit stav git.",
"workspace.status.clean": "Nebyly zjištěny žádné nesloučené změny.",
"workspace.status.dirty": "V tomto pracovním prostoru byly zjištěny nesloučené změny.",
+39 -39
View File
@@ -132,8 +132,8 @@ export const dict = {
"provider.connect.method.apiKey": "API-nøgle",
"provider.connect.method.browser": "Browser",
"provider.connect.method.headless": "Browserløs",
"provider.connect.status.inProgress": "Godkendelse i gang",
"provider.connect.status.waiting": "Venter på godkendelse",
"provider.connect.status.inProgress": "Godkendelse i gang...",
"provider.connect.status.waiting": "Venter på godkendelse...",
"provider.connect.status.failed": "Godkendelse mislykkedes: {{error}}",
"provider.connect.apiKey.description":
"Indtast din API-nøgle til {{provider}} for at forbinde din konto og bruge modeller fra {{provider}} i OpenCode.",
@@ -229,7 +229,7 @@ export const dict = {
"common.goBack": "Gå tilbage",
"common.goForward": "Naviger fremad",
"common.loading": "Indlæser",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Annuller",
"common.clear": "Ryd",
"common.connect": "Forbind",
@@ -237,13 +237,13 @@ export const dict = {
"common.continue": "Fortsæt",
"common.submit": "Indsend",
"common.save": "Gem",
"common.saving": "Gemmer",
"common.saving": "Gemmer...",
"common.default": "Standard",
"common.attachment": "vedhæftning",
"prompt.placeholder.shell": "Indtast shell-kommando {{example}}",
"prompt.placeholder.normal": 'Spørg om hvad som helst "{{example}}"',
"prompt.placeholder.simple": "Spørg om hvad som helst",
"prompt.placeholder.shell": "Indtast shell-kommando... {{example}}",
"prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"',
"prompt.placeholder.simple": "Spørg om hvad som helst...",
"prompt.placeholder.summarizeComments": "Opsummér kommentarer…",
"prompt.placeholder.summarizeComment": "Opsummér kommentar…",
"prompt.mode.shell": "Shell",
@@ -264,7 +264,7 @@ export const dict = {
"prompt.example.10": "Generer API-dokumentation",
"prompt.example.11": "Optimer databaseforespørgsler",
"prompt.example.12": "Tilføj validering af input",
"prompt.example.13": "Opret en ny komponent til",
"prompt.example.13": "Opret en ny komponent til...",
"prompt.example.14": "Hvordan deployerer jeg dette projekt?",
"prompt.example.15": "Gennemgå min kode for bedste praksis",
"prompt.example.16": "Tilføj fejlhåndtering til denne funktion",
@@ -275,7 +275,7 @@ export const dict = {
"prompt.example.21": "Hjælp mig med at skrive et migreringsscript",
"prompt.example.22": "Implementer caching for dette endpoint",
"prompt.example.23": "Tilføj sideinddeling til denne liste",
"prompt.example.24": "Opret en CLI-kommando til",
"prompt.example.24": "Opret en CLI-kommando til...",
"prompt.example.25": "Hvordan fungerer miljøvariabler her?",
"prompt.popover.emptyResults": "Ingen matchende resultater",
@@ -342,7 +342,7 @@ export const dict = {
"dialog.server.add.url": "Serveradresse",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Kunne ikke forbinde til server",
"dialog.server.add.checking": "Tjekker",
"dialog.server.add.checking": "Tjekker...",
"dialog.server.add.button": "Tilføj server",
"dialog.server.add.name": "Servernavn (valgfrit)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -367,26 +367,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-server",
"wsl.server.retryStart": "Prøv at starte igen",
"wsl.server.updating": "Opdaterer",
"wsl.server.updating": "Opdaterer...",
"wsl.onboarding.step.distro": "Vælg distribution",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Tjekker WSL",
"wsl.onboarding.checkingRuntime": "Tjekker WSL...",
"wsl.onboarding.restartRequired": "Windows skal genstartes for at fuldføre installationen af WSL.",
"wsl.onboarding.ready": "WSL er klar.",
"wsl.onboarding.required": "WSL er påkrævet for at fortsætte.",
"wsl.onboarding.checkingDistros": "Tjekker distributioner",
"wsl.onboarding.installingDistro": "Installerer {{distro}}",
"wsl.onboarding.checkingDistro": "Tjekker {{distro}}",
"wsl.onboarding.listingDistros": "Henter liste over distributioner",
"wsl.onboarding.checkingDistros": "Tjekker distributioner...",
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
"wsl.onboarding.checkingDistro": "Tjekker {{distro}}...",
"wsl.onboarding.listingDistros": "Henter liste over distributioner...",
"wsl.onboarding.distroReady": "{{distro}} er klar.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installeret endnu.",
"wsl.onboarding.openDistroOnce": "Åbn {{distro}} én gang for at fuldføre opsætningen.",
"wsl.onboarding.finishingDistro": "Fuldfører opsætningen af {{distro}}.",
"wsl.onboarding.pickDistro": "Vælg en distribution, eller installer en nedenfor.",
"wsl.onboarding.checkingOpencode": "Tjekker OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}",
"wsl.onboarding.updatingOpencode": "Opdaterer OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}",
"wsl.onboarding.checkingOpencode": "Tjekker OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}...",
"wsl.onboarding.updatingOpencode": "Opdaterer OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Opdater OpenCode i {{distro}}.",
"wsl.onboarding.updateOpencode": "Opdater OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
@@ -395,11 +395,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Vælg først en distribution.",
"wsl.onboarding.loadFailed": "Kunne ikke indlæse WSL-status.",
"wsl.onboarding.loading": "Indlæser",
"wsl.onboarding.loading": "Indlæser...",
"wsl.onboarding.installedDistros": "Installerede distributioner",
"wsl.onboarding.checkAgain": "Tjek igen",
"wsl.onboarding.distroStatus.ready": "Klar",
"wsl.onboarding.distroStatus.checking": "Tjekker",
"wsl.onboarding.distroStatus.checking": "Tjekker...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installeret",
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ikke understøttet · Brug WSL 2",
@@ -418,7 +418,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Alle installerede distributioner er allerede tilføjet.",
"wsl.onboarding.noDistros": "Ingen distributioner fundet endnu.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installerer",
"wsl.onboarding.installing": "Installerer...",
"wsl.onboarding.installDistro": "Installer distribution",
"wsl.onboarding.searchDistros": "Søg efter distributioner",
"wsl.onboarding.wsl2Required": "WSL 2 er påkrævet.",
@@ -430,7 +430,7 @@ export const dict = {
"wsl.onboarding.unknown": "ukendt",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Den installerede version matcher ikke desktopappens version.",
"wsl.onboarding.adding": "Tilføjer",
"wsl.onboarding.adding": "Tilføjer...",
"dialog.project.edit.title": "Rediger projekt",
"dialog.project.edit.name": "Navn",
@@ -552,7 +552,7 @@ export const dict = {
"error.page.action.report": "Rapportér fejl",
"error.page.action.reported": "Fejl rapporteret",
"error.page.action.exportLogs": "Eksportér logfiler",
"error.page.action.checking": "Tjekker",
"error.page.action.checking": "Tjekker...",
"error.page.action.checkUpdates": "Tjek for opdateringer",
"error.page.action.updateTo": "Opdater til {{version}}",
"error.page.report.prefix": "Rapporter venligst denne fejl til OpenCode-teamet",
@@ -629,7 +629,7 @@ export const dict = {
"session.review.filesChanged": "{{count}} ændrede filer",
"session.review.change.one": "Ændring",
"session.review.change.other": "Ændringer",
"session.review.loadingChanges": "Indlæser ændringer",
"session.review.loadingChanges": "Indlæser ændringer...",
"session.review.empty": "Ingen ændringer i denne session endnu",
"session.review.noVcs": "Intet Git-versionsstyringssystem fundet, så ændringer vises ikke",
"session.review.noSnapshot":
@@ -642,9 +642,9 @@ export const dict = {
"session.files.empty": "Ingen filer",
"session.files.binaryContent": "Binær fil (indhold kan ikke vises)",
"session.messages.renderEarlier": "Vis tidligere beskeder",
"session.messages.loadingEarlier": "Indlæser tidligere beskeder",
"session.messages.loadingEarlier": "Indlæser tidligere beskeder...",
"session.messages.loadEarlier": "Indlæs tidligere beskeder",
"session.messages.loading": "Indlæser beskeder",
"session.messages.loading": "Indlæser beskeder...",
"session.messages.jumpToLatest": "Gå til seneste",
"session.context.addToContext": "Tilføj {{selection}} til kontekst",
@@ -691,8 +691,8 @@ export const dict = {
"lsp.tooltip.none": "Ingen LSP-servere",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Indlæser prompt",
"terminal.loading": "Indlæser terminal",
"prompt.loading": "Indlæser prompt...",
"terminal.loading": "Indlæser terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Luk terminal",
@@ -812,9 +812,9 @@ export const dict = {
"settings.updates.row.check.title": "Tjek for opdateringer",
"settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig",
"settings.updates.action.checkNow": "Tjek nu",
"settings.updates.action.checking": "Tjekker",
"settings.updates.action.downloading": "Downloader",
"settings.updates.action.installing": "Installerer",
"settings.updates.action.checking": "Tjekker...",
"settings.updates.action.downloading": "Downloader...",
"settings.updates.action.installing": "Installerer...",
"settings.updates.toast.latest.title": "Du er opdateret",
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
@@ -970,7 +970,7 @@ export const dict = {
"workspace.reset.success.title": "Arbejdsområde nulstillet",
"workspace.reset.success.description": "Arbejdsområdet matcher nu hovedgrenen.",
"workspace.error.stillPreparing": "Arbejdsområdet er stadig ved at blive klargjort",
"workspace.status.checking": "Tjekker for uflettede ændringer",
"workspace.status.checking": "Tjekker for uflettede ændringer...",
"workspace.status.error": "Kunne ikke bekræfte Git-status.",
"workspace.status.clean": "Ingen uflettede ændringer fundet.",
"workspace.status.dirty": "Uflettede ændringer fundet i dette arbejdsområde.",
@@ -1000,12 +1000,12 @@ export const dict = {
"settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.",
"app.server.unreachable": "Kunne ikke nå {{server}}",
"app.server.retrying": "Prøver igen automatisk",
"app.server.retrying": "Prøver igen automatisk...",
"app.server.otherServers": "Andre servere",
"dialog.server.add.passwordPlaceholder": "adgangskode",
"session.review.noVcs.createGit.title": "Opret et Git-repository",
"session.review.noVcs.createGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt",
"session.review.noVcs.createGit.actionLoading": "Opretter Git-repository",
"session.review.noVcs.createGit.actionLoading": "Opretter Git-repository...",
"session.review.noVcs.createGit.action": "Opret Git-repository",
"session.todo.progress": "{{done}} af {{total}} opgaver fuldført",
"session.question.progress": "{{current}} af {{total}} spørgsmål",
@@ -1090,13 +1090,13 @@ export const dict = {
"desktop.menu.go": "Gå",
"desktop.menu.window": "Vindue",
"desktop.menu.help": "Hjælp",
"desktop.menu.checkForUpdates": "Søg efter opdateringer",
"desktop.menu.checkForUpdates": "Søg efter opdateringer...",
"desktop.menu.settings": "Indstillinger",
"desktop.menu.reloadWebview": "Genindlæs webvisning",
"desktop.menu.restart": "Genstart",
"desktop.menu.exportLogs": "Eksportér logfiler",
"desktop.menu.exportLogs": "Eksportér logfiler...",
"desktop.menu.newSession": "Ny session",
"desktop.menu.openProject": "Åbn projekt",
"desktop.menu.openProject": "Åbn projekt...",
"desktop.menu.newWindow": "Nyt vindue",
"desktop.menu.closeWindow": "Luk vindue",
"desktop.menu.undo": "Fortryd",
@@ -235,14 +235,14 @@ export const DESKTOP_NATIVE_ENGLISH = {
"desktop.menu.go": "Go",
"desktop.menu.window": "Window",
"desktop.menu.help": "Help",
"desktop.menu.checkForUpdates": "Check for Updates",
"desktop.menu.installCli": "Install CLI",
"desktop.menu.checkForUpdates": "Check for Updates...",
"desktop.menu.installCli": "Install CLI...",
"desktop.menu.settings": "Settings",
"desktop.menu.reloadWebview": "Reload Webview",
"desktop.menu.restart": "Restart",
"desktop.menu.exportLogs": "Export Logs",
"desktop.menu.exportLogs": "Export Logs...",
"desktop.menu.newSession": "New Session",
"desktop.menu.openProject": "Open Project",
"desktop.menu.openProject": "Open Project...",
"desktop.menu.newWindow": "New Window",
"desktop.menu.closeWindow": "Close Window",
"desktop.menu.undo": "Undo",
+41 -41
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "ދޭ",
"desktop.menu.window": "ކުޑަދޮރު",
"desktop.menu.help": "އެހީވުން",
"desktop.menu.checkForUpdates": "އަޕްޑޭޓްސް އަށް ޗެކް ކޮށްލައްވާ",
"desktop.menu.checkForUpdates": "އަޕްޑޭޓްސް އަށް ޗެކް ކޮށްލައްވާ...",
"desktop.menu.settings": "ސެޓިންގސް",
"desktop.menu.reloadWebview": "ވެބްވިއު ރީލޯޑް ކުރާށެވެ",
"desktop.menu.restart": "އަލުން ފަށާށެވެ",
"desktop.menu.exportLogs": "އެކްސްޕޯޓް ލޮގްސް",
"desktop.menu.exportLogs": "އެކްސްޕޯޓް ލޮގްސް...",
"desktop.menu.newSession": "އައު ސެޝަން",
"desktop.menu.openProject": "އޯޕަން ޕްރޮޖެކްޓް",
"desktop.menu.openProject": "އޯޕަން ޕްރޮޖެކްޓް...",
"desktop.menu.newWindow": "ނިއު ވިންޑޯ",
"desktop.menu.closeWindow": "ވިންޑޯ ބަންދުކުރުން",
"desktop.menu.undo": "އަންޑޯ",
@@ -216,8 +216,8 @@ export const dict = {
"provider.connect.method.apiKey": "API ކީ އެވެ",
"provider.connect.method.browser": "ބްރައުޒާ އެވެ",
"provider.connect.method.headless": "އިންޓަފޭސް ނެތި",
"provider.connect.status.inProgress": "ހުއްދަ ދިނުމުގެ މަސައްކަތް ކުރިއަށްދާ",
"provider.connect.status.waiting": "ހުއްދައަށް އިންތިޒާރު ކުރަމުން",
"provider.connect.status.inProgress": "ހުއްދަ ދިނުމުގެ މަސައްކަތް ކުރިއަށްދާ...",
"provider.connect.status.waiting": "ހުއްދައަށް އިންތިޒާރު ކުރަމުން...",
"provider.connect.status.failed": "ހުއްދަ ދިނުން ނާކާމިޔާބު: {{error}}",
"provider.connect.apiKey.description":
"ތިބާގެ އެކައުންޓް ގުޅުވައި، OpenCode ގައި {{provider}} މޮޑެލްތައް ބޭނުންކުރުމަށް {{provider}} API ކީ ލިޔުއްވާ.",
@@ -312,7 +312,7 @@ export const dict = {
"common.goBack": "ފަހަތަށް ދަތުރުކޮށްލާށެވެ",
"common.goForward": "ކުރިއަށް ދަތުރުކޮށްލާށެވެ",
"common.loading": "ލޯޑިންގ",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "ކެންސަލް",
"common.clear": "ސާފުކޮށް",
"common.open": "ހުޅުވުން",
@@ -321,14 +321,14 @@ export const dict = {
"common.continue": "ކުރިއަށްގެންދިޔުން",
"common.submit": "ހުށަހެޅުން",
"common.save": "ރައްކާކުރުން",
"common.saving": "ސޭވިންގ",
"common.saving": "ސޭވިންގ...",
"common.default": "ޑީފޯލްޓް",
"common.attachment": "އެކުލެވުން",
"prompt.placeholder.shell": "ޝެލް ކޮމާންޑް ލިޔުއްވާ {{example}}",
"prompt.placeholder.normal": 'ކޮންމެ އެއްޗަކާ އަހާލަން "{{example}}".',
"prompt.placeholder.simple": "ކޮންމެ އެއްޗެއް އަހާލަން",
"prompt.placeholder.summarizeComments": "ކޮމެންޓްތައް ޚުލާޞާކޮށްލައްވާ",
"prompt.placeholder.summarizeComment": "ކޮމެންޓް ޚުލާޞާކޮށްލައްވާ",
"prompt.placeholder.shell": "ޝެލް ކޮމާންޑް ލިޔުއްވާ... {{example}}",
"prompt.placeholder.normal": 'ކޮންމެ އެއްޗަކާ އަހާލަން... "{{example}}".',
"prompt.placeholder.simple": "ކޮންމެ އެއްޗެއް އަހާލަން...",
"prompt.placeholder.summarizeComments": "ކޮމެންޓްތައް ޚުލާޞާކޮށްލައްވާ...",
"prompt.placeholder.summarizeComment": "ކޮމެންޓް ޚުލާޞާކޮށްލައްވާ...",
"prompt.mode.shell": "ޝެލް",
"prompt.mode.normal": "ޕްރޮމްޕްޓް",
"prompt.mode.shell.exit": "esc އިން ނުކުތުމަށްޓަކައެވެ",
@@ -346,7 +346,7 @@ export const dict = {
"prompt.example.10": "API ޑޮކިއުމަންޓޭޝަން ޖެނެރޭޓްކުރުން",
"prompt.example.11": "ޑޭޓާބޭސް ކިއުއަރީތައް އޮޕްޓިމައިޒް ކުރުން",
"prompt.example.12": "އިންޕުޓް ވެލިޑޭޝަން އިތުރުކުރުން",
"prompt.example.13": "އަށް އާ ކޮމްޕޮނެންޓެއް އުފެއްދުން",
"prompt.example.13": "އަށް އާ ކޮމްޕޮނެންޓެއް އުފެއްދުން...",
"prompt.example.14": "މި ޕްރޮޖެކްޓް ޑިޕްލޮއި ކުރާނީ ކިހިނެއް ހެއްޔެވެ؟",
"prompt.example.15": "އެންމެ ރަނގަޅު އުސޫލުތަކަށް އަހަރެންގެ ކޯޑް ރިވިއުކުރުން",
"prompt.example.16": "މި ފަންކްޝަނަށް އެރަރ ހެންޑްލިންގ އިތުރުކުރުން",
@@ -410,7 +410,7 @@ export const dict = {
"dialog.directory.parent": "ބެލެނިވެރިޔާއެވެ",
"dialog.directory.readError": "މި ފޯލްޑަރ ކިޔަން ނުކުޅެދިއްޖެއެވެ",
"app.server.unreachable": "{{server}} އަށް ވާސިލްވެވޭ ގޮތެއް ނުވިއެވެ",
"app.server.retrying": "އޮޓޮމެޓިކުން އަލުން މަސައްކަތް ކުރަނީ",
"app.server.retrying": "އޮޓޮމެޓިކުން އަލުން މަސައްކަތް ކުރަނީ...",
"app.server.otherServers": "އެހެން ސަރވަރތަކެވެ",
"dialog.server.title": "ސަރވަރސް",
"dialog.server.description": "މި އެޕް ގުޅޭނީ ކޮން OpenCode ސަރވަރަކާ ބަދަލުކޮށްލާށެވެ.",
@@ -420,7 +420,7 @@ export const dict = {
"dialog.server.add.url": "ސަރވަރ އެޑްރެސް",
"dialog.server.add.placeholder": "http://localhost:4096 އެވެ",
"dialog.server.add.error": "ސަރވަރ އާއި ގުޅެވޭ ގޮތެއް ނުވިއެވެ",
"dialog.server.add.checking": "ޗެކް ކުރަމުން",
"dialog.server.add.checking": "ޗެކް ކުރަމުން...",
"dialog.server.add.button": "ސަރވަރ އިތުރުކުރުން",
"dialog.server.add.name": "ސަރވަރ ނަން (އިޚްތިޔާރީ)",
"dialog.server.add.namePlaceholder": "Localhost އެވެ",
@@ -445,26 +445,26 @@ export const dict = {
"wsl.server.label": "WSL އެވެ",
"wsl.server.menu.label": "WSL ސަރވަރ އެވެ",
"wsl.server.retryStart": "އަލުން ފަށަން މަސައްކަތް ކުރާށެވެ",
"wsl.server.updating": "އަޕްޑޭޓް ކުރަމުން",
"wsl.server.updating": "އަޕްޑޭޓް ކުރަމުން...",
"wsl.onboarding.step.distro": "ޑިސްޓްރޯ ހޮވާށެވެ",
"wsl.onboarding.step.opencode": "OpenCode އެވެ",
"wsl.onboarding.checkingRuntime": "ޗެކް ކުރަމުންދަނީ WSL",
"wsl.onboarding.checkingRuntime": "ޗެކް ކުރަމުންދަނީ WSL...",
"wsl.onboarding.restartRequired": "Windows އިންސްޓޯލް ކުރުން ނިންމުމަށް ރީސްޓާޓް އެއް ބޭނުންވެއެވެ.",
"wsl.onboarding.ready": "WSL ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.required": "WSL ކުރިއަށް ގެންދަން ޖެހެ އެވެ.",
"wsl.onboarding.checkingDistros": "ޗެކިންގ ޑިސްޓްރޯސް",
"wsl.onboarding.installingDistro": "{{distro}} އިންސްޓޯލް ކުރަމުން",
"wsl.onboarding.checkingDistro": "ޗެކް ކުރަމުންދަނީ {{distro}}",
"wsl.onboarding.listingDistros": "ލިސްޓިން ޑިސްޓްރޯސް",
"wsl.onboarding.checkingDistros": "ޗެކިންގ ޑިސްޓްރޯސް...",
"wsl.onboarding.installingDistro": "{{distro}} އިންސްޓޯލް ކުރަމުން...",
"wsl.onboarding.checkingDistro": "ޗެކް ކުރަމުންދަނީ {{distro}}...",
"wsl.onboarding.listingDistros": "ލިސްޓިން ޑިސްޓްރޯސް...",
"wsl.onboarding.distroReady": "{{distro}} ތައްޔާރުވެއްޖެއެވެ.",
"wsl.onboarding.distroNotInstalled": "{{distro}} އަދި އިންސްޓޯލްކޮށްފައެއް ނުވެއެވެ.",
"wsl.onboarding.openDistroOnce": "ސެޓަޕް ނިންމާލުމަށްޓަކައި {{distro}} އެއްފަހަރު ހުޅުވާލާށެވެ.",
"wsl.onboarding.finishingDistro": "{{distro}} އަށް ފިނިޝިންގ ސެޓަޕް.",
"wsl.onboarding.pickDistro": "ޑިސްޓްރޯއެއް ހޮވާ ނުވަތަ ތިރީގައިވާ ޑިސްޓްރޯއެއް އިންސްޓޯލް ކުރާށެވެ.",
"wsl.onboarding.checkingOpencode": "ޗެކް ކުރަމުންދަނީ OpenCode",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} ގައި OpenCode ޗެކްކުރަނީ",
"wsl.onboarding.updatingOpencode": "އަޕްޑޭޓް ކުރަމުންދަނީ OpenCode",
"wsl.onboarding.updatingOpencodeIn": "{{distro}} ގައި OpenCode އަޕްޑޭޓް ކުރަމުންދާ",
"wsl.onboarding.checkingOpencode": "ޗެކް ކުރަމުންދަނީ OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} ގައި OpenCode ޗެކްކުރަނީ...",
"wsl.onboarding.updatingOpencode": "އަޕްޑޭޓް ކުރަމުންދަނީ OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}} ގައި OpenCode އަޕްޑޭޓް ކުރަމުންދާ...",
"wsl.onboarding.updateOpencodeIn": "{{distro}} ގައި OpenCode އަޕްޑޭޓް ކުރާށެވެ.",
"wsl.onboarding.updateOpencode": "OpenCode އަޕްޑޭޓް ކުރާށެވެ",
"wsl.onboarding.opencodeReadyIn": "OpenCode ތައްޔާރުކޮށްފައިވަނީ {{distro}} ގައެވެ.",
@@ -473,11 +473,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "OpenCode އިންސްޓޯލް ކުރާށެވެ",
"wsl.onboarding.chooseDistroFirst": "ފުރަތަމަ ޑިސްޓްރޯއެއް ހޮވާށެވެ.",
"wsl.onboarding.loadFailed": "WSL ސްޓޭޓް ލޯޑް ނުކުރެވިއްޖެއެވެ.",
"wsl.onboarding.loading": "ލޯޑިންގ",
"wsl.onboarding.loading": "ލޯޑިންގ...",
"wsl.onboarding.installedDistros": "އިންސްޓޯލް ކޮށްފައިވާ ޑިސްޓްރޯސް",
"wsl.onboarding.checkAgain": "އަނެއްކާވެސް ޗެކް ކުރާށެވެ",
"wsl.onboarding.distroStatus.ready": "ތައްޔާރު",
"wsl.onboarding.distroStatus.checking": "ޗެކް ކުރަމުން",
"wsl.onboarding.distroStatus.checking": "ޗެކް ކުރަމުން...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode އިންސްޓޯލްކޮށްފައެއް ނުވެއެވެ",
"wsl.onboarding.distroStatus.missingTools": "ގެއްލިފައިވަނީ bash، curl އެވެ",
"wsl.onboarding.distroStatus.unsupported": "ސަޕޯޓް ނުކުރެވޭ · WSL ބޭނުންކުރުން 2",
@@ -496,7 +496,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "އިންސްޓޯލްކޮށްފައިވާ ހުރިހާ ޑިސްޓްރޯތަކެއް މިހާރުވެސް އިތުރުކޮށްފައިވެއެވެ.",
"wsl.onboarding.noDistros": "އަދި ޑިސްޓްރޯއެއް ހޯދިފައެއް ނުވެއެވެ.",
"wsl.onboarding.install": "އެޅުން",
"wsl.onboarding.installing": "އިންސްޓޯލް ކުރަމުން",
"wsl.onboarding.installing": "އިންސްޓޯލް ކުރަމުން...",
"wsl.onboarding.installDistro": "ޑިސްޓްރޯ އިންސްޓޯލް ކުރާށެވެ",
"wsl.onboarding.searchDistros": "ޑިސްޓްރޯސް ހޯދުން",
"wsl.onboarding.wsl2Required": "WSL 2 ބޭނުންވެއެވެ.",
@@ -508,7 +508,7 @@ export const dict = {
"wsl.onboarding.unknown": "ނޭނގޭ އެއްޗެއް",
"wsl.onboarding.desktopVersion": "ޑެސްކްޓޮޕް {{version}} އެވެ",
"wsl.onboarding.versionMismatch": "އިންސްޓޯލް ކޮށްފައިވާ ވަރޝަން ޑެސްކްޓޮޕް އެޕް ވަރޝަން އާއި އެއްގޮތެއް ނުވެއެވެ.",
"wsl.onboarding.adding": "އިތުރުކުރަނީ",
"wsl.onboarding.adding": "އިތުރުކުރަނީ...",
"dialog.project.edit.title": "އެޑިޓް ޕްރޮޖެކްޓް",
"dialog.project.edit.name": "ނަން",
"dialog.project.edit.icon": "އައިކޮން",
@@ -614,7 +614,7 @@ export const dict = {
"error.page.action.report": "ރިޕޯޓް އެރަރ",
"error.page.action.reported": "އެރަރ ރިޕޯޓް ކުރެވިއްޖެއެވެ",
"error.page.action.exportLogs": "އެކްސްޕޯޓް ލޮގްސް",
"error.page.action.checking": "ޗެކް ކުރަމުން",
"error.page.action.checking": "ޗެކް ކުރަމުން...",
"error.page.action.checkUpdates": "އަޕްޑޭޓްތައް ހުރިތޯ ބަލާށެވެ",
"error.page.action.updateTo": "{{version}} އަށް އަޕްޑޭޓް ކުރާށެވެ",
"error.page.circular": "[ސާކިއުލާ]",
@@ -696,12 +696,12 @@ export const dict = {
"session.review.filesChanged": "ފައިލްތައް ބަދަލުވެއްޖެ {{count}}",
"session.review.change.one": "ބަދަލު",
"session.review.change.other": "ބަދަލުތައް",
"session.review.loadingChanges": "ބަދަލުތައް ލޯޑް ކުރަނީ",
"session.review.loadingChanges": "ބަދަލުތައް ލޯޑް ކުރަނީ...",
"session.review.empty": "މި ޖަލްސާއަށް އަދި ބަދަލެއް ނާދެއެވެ",
"session.review.noVcs": "Git ވަރޝަން ކޮންޓްރޯލް ސިސްޓަމެއް ނުފެނި، ބަދަލުތައް ނުފެނެއެވެ",
"session.review.noVcs.createGit.title": "Git ރިޕޮޒިޓަރީއެއް އުފެއްދުން",
"session.review.noVcs.createGit.description": "މި މަޝްރޫޢަށް އަންނަ ބަދަލުތައް ޓްރެކްކޮށް، ރިވިއުކޮށް، އަންޑޯކުރުން",
"session.review.noVcs.createGit.actionLoading": "Git ރިޕޮޒިޓަރީ އުފެއްދުމުގެ މަސައްކަތް",
"session.review.noVcs.createGit.actionLoading": "Git ރިޕޮޒިޓަރީ އުފެއްދުމުގެ މަސައްކަތް...",
"session.review.noVcs.createGit.action": "Git ރިޕޮޒިޓަރީ އުފެއްދުން",
"session.review.noSnapshot":
"ކޮންފިގްގައި ސްނެޕްޝޮޓް ޓްރެކިންގ ޑިސެބިލް ކޮށްފައި ހުރުމުން ސެޝަން ބަދަލުތައް ނުލިބެއެވެ",
@@ -713,9 +713,9 @@ export const dict = {
"session.files.empty": "ފައިލްތަކެއް ނެތެވެ",
"session.files.binaryContent": "ބައިނަރީ ފައިލް (ކޮންޓެންޓް ނުދެއްކޭނެ)",
"session.messages.renderEarlier": "ކުރީގެ މެސެޖުތައް ރެންޑަރ ކުރާށެވެ",
"session.messages.loadingEarlier": "ކުރީގެ މެސެޖުތައް ލޯޑް ކުރަނީ",
"session.messages.loadingEarlier": "ކުރީގެ މެސެޖުތައް ލޯޑް ކުރަނީ...",
"session.messages.loadEarlier": "ކުރީގެ މެސެޖުތައް ލޯޑް ކުރާށެވެ",
"session.messages.loading": "މެސެޖުތައް ލޯޑް ކުރަނީ",
"session.messages.loading": "މެސެޖުތައް ލޯޑް ކުރަނީ...",
"session.messages.jumpToLatest": "އެންމެ ފަހުގެ ވާހަކައަށް ފުންމާލާށެވެ",
"session.context.addToContext": "ކޮންޓެކްސްޓަށް {{selection}} އިތުރުކުރުން",
"session.todo.title": "ޓޮޑޯސް އެވެ",
@@ -737,7 +737,7 @@ export const dict = {
"session.new.workspace.runIn": "ރަން ސެޝަން އިން",
"session.new.workspace.triggerLocal": "ލޯކަލް",
"session.new.workspace.local": "ލޯކަލް ރިޕޮޒިޓަރީ",
"session.new.workspace.existing": "ވޯކްސްޕޭސް",
"session.new.workspace.existing": "ވޯކްސްޕޭސް...",
"session.new.git.none": "އެއްވެސް Git އެއް ނެތެވެ",
"session.new.lastModified": "އެންމެ ފަހުން ބަދަލުކުރީ",
"session.header.search.placeholder": "{{project}} ހޯދުން",
@@ -776,8 +776,8 @@ export const dict = {
"common.copied": "ކޮޕީކޮށްފައި",
"lsp.tooltip.none": "LSP ސަރވަރެއް ނެތެވެ",
"lsp.label.connected": "{{count}} LSP އެވެ",
"prompt.loading": "ލޯޑިންގ ޕްރޮމްޕްޓް",
"terminal.loading": "ލޯޑިންގ ޓާމިނަލް",
"prompt.loading": "ލޯޑިންގ ޕްރޮމްޕްޓް...",
"terminal.loading": "ލޯޑިންގ ޓާމިނަލް...",
"terminal.title": "ޓާމިނަލް އެވެ",
"terminal.title.numbered": "ޓާމިނަލް {{number}} އެވެ",
"terminal.close": "ޓާމިނަލް ބަންދުކުރުން",
@@ -943,9 +943,9 @@ export const dict = {
"settings.updates.row.check.title": "އަޕްޑޭޓްތައް ހުރިތޯ ބަލާށެވެ",
"settings.updates.row.check.description": "އަޕްޑޭޓްތައް މެނުއަލްކޮށް ޗެކްކޮށް، ލިބޭނަމަ އިންސްޓޯލް ކުރާށެވެ",
"settings.updates.action.checkNow": "މިހާރު ޗެކް ކޮށްލައްވާ",
"settings.updates.action.checking": "ޗެކް ކުރަމުން",
"settings.updates.action.downloading": "ޑައުންލޯޑް ކުރަމުން",
"settings.updates.action.installing": "އިންސްޓޯލް ކުރަމުން",
"settings.updates.action.checking": "ޗެކް ކުރަމުން...",
"settings.updates.action.downloading": "ޑައުންލޯޑް ކުރަމުން...",
"settings.updates.action.installing": "އިންސްޓޯލް ކުރަމުން...",
"settings.updates.toast.latest.title": "ޔޫ އާ އަޕް ޓު ޑޭޓް",
"settings.updates.toast.latest.description": "ތިޔަ ހިންގަނީ OpenCode ގެ އެންމެ ފަހުގެ ވަރޝަން އެވެ.",
"sound.option.none": "އެއްޗެއްނޫން",
@@ -1096,7 +1096,7 @@ export const dict = {
"workspace.reset.success.title": "ވޯކްސްޕޭސް ރީސެޓް ކުރުން",
"workspace.reset.success.description": "ވޯކްސްޕޭސް މިހާރު ޑިފޯލްޓް ބްރާންޗާ ދިމާވެއެވެ.",
"workspace.error.stillPreparing": "ވޯކްސްޕޭސް އަދިވެސް ދަނީ ތައްޔާރުވަމުންނެވެ",
"workspace.status.checking": "އެއް ނުކުރެވި ހުރި ބަދަލުތައް ޗެކް ކުރަމުން",
"workspace.status.checking": "އެއް ނުކުރެވި ހުރި ބަދަލުތައް ޗެކް ކުރަމުން...",
"workspace.status.error": "ގިޓް ސްޓޭޓަސް ޔަގީން ނުކުރެވެއެވެ.",
"workspace.status.clean": "އެއްނުވާ ބަދަލެއް ފާހަގަކުރެވިފައެއް ނުވެއެވެ.",
"workspace.status.dirty": "މި ވޯކްސްޕޭސްގައި ފާހަގަ ނުކުރެވި ހުރި ބަދަލުތައް.",
+20 -20
View File
@@ -10,9 +10,9 @@ export const dict: Record<string, string> = {
"desktop.menu.settings": "སྒྲིག་སྟངས་ཚུ།",
"desktop.menu.reloadWebview": "ཡང་བསྐྱར་མངོན་གསལ་ Webview།",
"desktop.menu.restart": "ལོག་འགོ་བཙུགས།",
"desktop.menu.exportLogs": "ཕྱིར་འདྲེན་དྲན་ཐོ་",
"desktop.menu.exportLogs": "ཕྱིར་འདྲེན་དྲན་ཐོ་...",
"desktop.menu.newSession": "ལཱ་ཡུན་གསརཔ།",
"desktop.menu.openProject": "ལས་འགུལ་ཁ་ཕྱེ།",
"desktop.menu.openProject": "ལས་འགུལ་ཁ་ཕྱེ།...",
"desktop.menu.newWindow": "སྒོ་སྒྲིག་གསརཔ།",
"desktop.menu.closeWindow": "སྒོ་སྒྲིག་ཁ་བསྡམས།",
"desktop.menu.undo": "འབད་བཤོལ་འབད།",
@@ -311,7 +311,7 @@ export const dict: Record<string, string> = {
"common.goBack": "ལོག་འགྲུལ་བསྐྱོད་འབད།",
"common.goForward": "གདོང་ཕྱོགས་ལུ་འགྲུལ་བསྐྱོད་འབད།",
"common.loading": "མངོན་གསལ་འབད་དོ།",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "ཆ་མེད་བཏང་པ་",
"common.clear": "གསལ་ཏོག་ཏོ",
"common.open": "ཕྱེ་ཕྱེཝ",
@@ -323,11 +323,11 @@ export const dict: Record<string, string> = {
"common.saving": "གསོག་འཇོག་འབད་དོ།",
"common.default": "འཐུས་ཤོར",
"common.attachment": "མཉམ༌སྦྲགས༌",
"prompt.placeholder.shell": "Shell བརྡ་བཀོད་བཙུགས། {{example}}",
"prompt.placeholder.normal": 'ག་ཅི་ཡང་དྲིས "{{example}}"',
"prompt.placeholder.shell": "Shell བརྡ་བཀོད་བཙུགས།... {{example}}",
"prompt.placeholder.normal": 'ག་ཅི་ཡང་དྲིས... "{{example}}"',
"prompt.placeholder.simple": "ག་ཅི་ཡང་དྲིས།",
"prompt.placeholder.summarizeComments": "བསམ་འཆར་ཚུ་བཅུད་བསྡུས་འབད།",
"prompt.placeholder.summarizeComment": "བསམ་འཆར་བཅུད་བསྡུས།",
"prompt.placeholder.summarizeComment": "བསམ་འཆར་བཅུད་བསྡུས།...",
"prompt.mode.shell": "Shell",
"prompt.mode.normal": "འདི་འཕྲོ་ལས",
"prompt.mode.shell.exit": "esc ཕྱིར་འཐོན་འབད་ནི།",
@@ -447,23 +447,23 @@ export const dict: Record<string, string> = {
"wsl.server.updating": "དུས་མཐུན་བཟོ་དོ།",
"wsl.onboarding.step.distro": "བཀྲམ་སྤེལ་གདམ་ཁ་རྐྱབས།",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "ཞིབ་དཔྱད་འབད་དོ WSL",
"wsl.onboarding.checkingRuntime": "ཞིབ་དཔྱད་འབད་དོ WSL...",
"wsl.onboarding.restartRequired": "Windowsལུ་ WSLགཞི་བཙུགས་མཇུག་བསྡུ་ནིའི་དོན་ལུ་ ལོག་འགོ་བཙུགས་དགོཔ་ཨིན།",
"wsl.onboarding.ready": "WSL གྲ་སྒྲིག་ཡོད།",
"wsl.onboarding.required": "WSL འདི་འཕྲོ་མཐུད་འབད་དགོཔ་ཨིན།",
"wsl.onboarding.checkingDistros": "བཀྲམ་སྤེལ་ཚུ་ཞིབ་དཔྱད་འབད་དོ།",
"wsl.onboarding.installingDistro": "{{distro}}གཞི་བཙུགས་འབད་དོ།",
"wsl.onboarding.checkingDistro": "ཞིབ་དཔྱད་འབད་དོ་ {{distro}}",
"wsl.onboarding.installingDistro": "{{distro}}གཞི་བཙུགས་འབད་དོ།...",
"wsl.onboarding.checkingDistro": "ཞིབ་དཔྱད་འབད་དོ་ {{distro}}...",
"wsl.onboarding.listingDistros": "ཐོ་བཀོད་འབད་ནི།",
"wsl.onboarding.distroReady": "{{distro}} གྲ་སྒྲིག་ཡོད།",
"wsl.onboarding.distroNotInstalled": "{{distro}} ད་ལྟོ་གཞི་བཙུགས་མ་འབད་བས།",
"wsl.onboarding.openDistroOnce": "གཞི་སྒྲིག་མཇུག་བསྡུ་ནིའི་དོན་ལུ་ {{distro}} ཚར་གཅིག་ཁ་ཕྱེ།",
"wsl.onboarding.finishingDistro": "{{distro}}གི་དོན་ལུ་གཞི་སྒྲིག་མཇུག་བསྡུ་དོ།",
"wsl.onboarding.pickDistro": "ཌིསི་ཊོ་ཅིག་གདམ་ཁ་རྐྱབ་ནི་ཡང་ན་ འོག་ལུ་གཅིག་གཞི་བཙུགས་འབད།",
"wsl.onboarding.checkingOpencode": "OpenCodeཞིབ་དཔྱད་འབད་དོ།",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}ནང་ OpenCode ཞིབ་དཔྱད་འབད་དོ།",
"wsl.onboarding.updatingOpencode": "OpenCodeདུས་མཐུན་བཟོ་དོ།",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}ནང་ OpenCodeདུས་མཐུན་བཟོ་དོ།",
"wsl.onboarding.checkingOpencode": "OpenCodeཞིབ་དཔྱད་འབད་དོ།...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}ནང་ OpenCode ཞིབ་དཔྱད་འབད་དོ།...",
"wsl.onboarding.updatingOpencode": "OpenCodeདུས་མཐུན་བཟོ་དོ།...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}ནང་ OpenCodeདུས་མཐུན་བཟོ་དོ།...",
"wsl.onboarding.updateOpencodeIn": "OpenCodeའདི་ {{distro}}ནང་དུས་མཐུན་བཟོ།",
"wsl.onboarding.updateOpencode": "OpenCodeདུས་མཐུན་བཟོ།",
"wsl.onboarding.opencodeReadyIn": "OpenCode འདི་ {{distro}} ནང་ལུ་གྲ་སྒྲིག་ཡོདཔ་ཨིན།",
@@ -695,13 +695,13 @@ export const dict: Record<string, string> = {
"session.review.filesChanged": "ཡིག་སྣོད་ཚུ་བསྒྱུར་བཅོས་འབད་ཡོདཔ། {{count}}",
"session.review.change.one": "འགྱུར་བ",
"session.review.change.other": "བསྒྱུར་བཅོས་ཚུ།",
"session.review.loadingChanges": "བསྒྱུར་བཅོས་ཚུ་མངོན་གསལ་འབད་དོ",
"session.review.loadingChanges": "བསྒྱུར་བཅོས་ཚུ་མངོན་གསལ་འབད་དོ...",
"session.review.empty": "ལཱ་ཡུན་འདི་ནང་ བསྒྱུར་བཅོས་ག་ནི་ཡང་མེདཔ།",
"session.review.noVcs": "མེན་ Git ཐོན་རིམ་ཚད་འཛིན་རིམ་ལུགས་སྐྱོན་འཛིན་འབད་ནུག བསྒྱུར་བཅོས་ཚུ་བཀྲམ་སྟོན་མ་འབད་བས།",
"session.review.noVcs.createGit.title": "Gitམཛོད་ཅིག་གསར་བསྐྲུན་འབད།",
"session.review.noVcs.createGit.description":
"ལས་འགུལ་འདི་ནང་ བསྒྱུར་བཅོས་ཚུ་ བརྟག་ཞིབ་འབད་ནི་དང་ བསྐྱར་ཞིབ་འབད་ནི་ དེ་ལས་ འབད་བཤོལ་འབད་ནི།",
"session.review.noVcs.createGit.actionLoading": "Git མཛོད་ཁང་གསར་བསྐྲུན་འབད་དོ།",
"session.review.noVcs.createGit.actionLoading": "Git མཛོད་ཁང་གསར་བསྐྲུན་འབད་དོ།...",
"session.review.noVcs.createGit.action": "Gitམཛོད་ཁང་གསར་བསྐྲུན་འབད།",
"session.review.noSnapshot":
"རིམ་སྒྲིག་ནང་ལུ་ པར་རིས་རྗེས་འདེད་འབད་ནི་འདི་ལྕོགས་མིན་བཟོ་ཡོདཔ་ལས་ ལཱ་ཡུན་བསྒྱུར་བཅོས་ཚུ་འཐོབ་མི་ཚུགས།",
@@ -713,9 +713,9 @@ export const dict: Record<string, string> = {
"session.files.empty": "ཡིག་སྣོད་ཚུ་མེད།",
"session.files.binaryContent": "ཟུང་ལྡན་ཡིག་སྣོད་ (ནང་དོན་བཀྲམ་སྟོན་འབད་མི་བཏུབ།)",
"session.messages.renderEarlier": "ཧེ་མའི་འཕྲིན་དོན་ཚུ་བཀྲམ་སྟོན་འབད།",
"session.messages.loadingEarlier": "ཧེ་མའི་འཕྲིན་དོན་ཚུ་མངོན་གསལ་འབད་དོ།",
"session.messages.loadingEarlier": "ཧེ་མའི་འཕྲིན་དོན་ཚུ་མངོན་གསལ་འབད་དོ།...",
"session.messages.loadEarlier": "ཧེ་མའི་འཕྲིན་དོན་ཚུ་མངོན་གསལ་འབད།",
"session.messages.loading": "འཕྲིན་དོན་ཚུ་མངོན་གསལ་འབད་དོ།",
"session.messages.loading": "འཕྲིན་དོན་ཚུ་མངོན་གསལ་འབད་དོ།...",
"session.messages.jumpToLatest": "གསར་ཤོས་ལུ་མཆོངས།",
"session.context.addToContext": "{{selection}} སྐབས་དོན་ལུ་ཁ་སྐོང་བརྐྱབ།",
"session.todo.title": "ཊོ་ཌོས།",
@@ -737,7 +737,7 @@ export const dict: Record<string, string> = {
"session.new.workspace.runIn": "ནང་ལཱ་ཡུན་གཡོག་བཀོལ།",
"session.new.workspace.triggerLocal": "ས་གནས་ཀྱི",
"session.new.workspace.local": "ས་གནས་ཀྱི་མཛོད་ཁང་།",
"session.new.workspace.existing": "ལཱ་གི་ས་སྒོ",
"session.new.workspace.existing": "ལཱ་གི་ས་སྒོ...",
"session.new.git.none": "མེད། Git།",
"session.new.lastModified": "མཐའ་མའི་བཟོ་བཅོས་འབད་ཡོདཔ།",
"session.header.search.placeholder": "འཚོལ་ཞིབ་ {{project}}།",
@@ -777,7 +777,7 @@ export const dict: Record<string, string> = {
"lsp.tooltip.none": "LSP སར་བར་ཚུ་མེད།",
"lsp.label.connected": "{{count}} LSP།",
"prompt.loading": "མངོན་གསལ་འབད་དོ།",
"terminal.loading": "ཊར་མི་ནཱལ་མངོན་གསལ་འབད་དོ་",
"terminal.loading": "ཊར་མི་ནཱལ་མངོན་གསལ་འབད་དོ་...",
"terminal.title": "ཊར་མི་ནཱལ།",
"terminal.title.numbered": "ཊར་མི་ནཱལ་ {{number}}།",
"terminal.close": "ཊར་མི་ནཱལ་ཁ་བསྡམས།",
@@ -1099,7 +1099,7 @@ export const dict: Record<string, string> = {
"workspace.reset.success.title": "ལཱ་གི་ས་སྒོ་སླར་སྒྲིག་འབད།",
"workspace.reset.success.description": "ལཱ་གི་ས་སྒོ་འདི་ད་ལྟོ་སྔོན་སྒྲིག་ཡན་ལག་ལུ་མཐུན་སྒྲིག་འབདཝ་ཨིན།",
"workspace.error.stillPreparing": "ལཱ་གི་ས་སྒོ་འདི་ ད་ལྟོ་ཡང་གྲ་སྒྲིག་འབད་དོ།",
"workspace.status.checking": "མཉམ་བསྡོམས་མ་འབད་བའི་བསྒྱུར་བཅོས་ཚུ་ཞིབ་དཔྱད་འབད་དོ",
"workspace.status.checking": "མཉམ་བསྡོམས་མ་འབད་བའི་བསྒྱུར་བཅོས་ཚུ་ཞིབ་དཔྱད་འབད་དོ...",
"workspace.status.error": "ཇིཊི་གནས་ཚད་བདེན་སྦྱོར་འབད་མ་ཚུགས།",
"workspace.status.clean": "མཉམ་བསྡོམས་མ་འབད་བའི་བསྒྱུར་བཅོས་ཚུ་ སྐྱོན་འཛིན་མ་འབད་བས།",
"workspace.status.dirty": "ལཱ་གི་ས་སྒོ་འདི་ནང་མཉམ་བསྡོམས་མ་འབད་བའི་བསྒྱུར་བཅོས་ཚུ་སྐྱོན་འཛིན་འབད་ཡོདཔ།",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Μετάβαση",
"desktop.menu.window": "Παράθυρο",
"desktop.menu.help": "Βοήθεια",
"desktop.menu.checkForUpdates": "Έλεγχος για ενημερώσεις",
"desktop.menu.checkForUpdates": "Έλεγχος για ενημερώσεις...",
"desktop.menu.settings": "Ρυθμίσεις",
"desktop.menu.reloadWebview": "Επανάληψη φόρτωσης Webview",
"desktop.menu.restart": "Επανεκκίνηση",
"desktop.menu.exportLogs": "Εξαγωγή αρχείων καταγραφής",
"desktop.menu.exportLogs": "Εξαγωγή αρχείων καταγραφής...",
"desktop.menu.newSession": "Νέα συνεδρία",
"desktop.menu.openProject": "Άνοιγμα έργου",
"desktop.menu.openProject": "Άνοιγμα έργου...",
"desktop.menu.newWindow": "Νέο παράθυρο",
"desktop.menu.closeWindow": "Κλείσιμο παραθύρου",
"desktop.menu.undo": "Αναίρεση",
@@ -213,8 +213,8 @@ export const dict = {
"provider.connect.method.apiKey": "API κλειδί",
"provider.connect.method.browser": "Πρόγραμμα περιήγησης",
"provider.connect.method.headless": "Χωρίς γραφικό περιβάλλον",
"provider.connect.status.inProgress": "Εξουσιοδότηση σε εξέλιξη",
"provider.connect.status.waiting": "Αναμονή για εξουσιοδότηση",
"provider.connect.status.inProgress": "Εξουσιοδότηση σε εξέλιξη...",
"provider.connect.status.waiting": "Αναμονή για εξουσιοδότηση...",
"provider.connect.status.failed": "Η εξουσιοδότηση απέτυχε: {{error}}",
"provider.connect.apiKey.description":
"Εισαγάγετε το κλειδί {{provider}} API για να συνδέσετε τον λογαριασμό σας και χρησιμοποιήστε μοντέλα {{provider}} στο OpenCode.",
@@ -308,7 +308,7 @@ export const dict = {
"common.goBack": "Πλοήγηση πίσω",
"common.goForward": "Πλοήγηση προς τα εμπρός",
"common.loading": "Φόρτωση",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Ακύρωση",
"common.clear": "Διαγραφή",
"common.open": "Άνοιγμα",
@@ -317,12 +317,12 @@ export const dict = {
"common.continue": "Συνέχεια",
"common.submit": "Υποβολή",
"common.save": "Αποθήκευση",
"common.saving": "Αποθήκευση",
"common.saving": "Αποθήκευση...",
"common.default": "Προεπιλογή",
"common.attachment": "συνημμένο",
"prompt.placeholder.shell": "Εισαγωγή εντολής κελύφους {{example}}",
"prompt.placeholder.normal": 'Ρωτήστε οτιδήποτε "{{example}}"',
"prompt.placeholder.simple": "Ρωτήστε οτιδήποτε",
"prompt.placeholder.shell": "Εισαγωγή εντολής κελύφους... {{example}}",
"prompt.placeholder.normal": 'Ρωτήστε οτιδήποτε... "{{example}}"',
"prompt.placeholder.simple": "Ρωτήστε οτιδήποτε...",
"prompt.placeholder.summarizeComments": "Σύνοψη σχολίων…",
"prompt.placeholder.summarizeComment": "Σύνοψη σχολίου…",
"prompt.mode.shell": "Shell",
@@ -342,7 +342,7 @@ export const dict = {
"prompt.example.10": "Δημιουργία API τεκμηρίωσης",
"prompt.example.11": "Βελτιστοποίηση ερωτημάτων βάσης δεδομένων",
"prompt.example.12": "Προσθήκη επικύρωσης εισόδου",
"prompt.example.13": "Δημιουργία νέου στοιχείου για",
"prompt.example.13": "Δημιουργία νέου στοιχείου για...",
"prompt.example.14": "Πώς μπορώ να αναπτύξω αυτό το έργο;",
"prompt.example.15": "Ελέγξτε τον κώδικά μου για βέλτιστες πρακτικές",
"prompt.example.16": "Προσθήκη χειρισμού σφαλμάτων σε αυτήν τη λειτουργία",
@@ -353,7 +353,7 @@ export const dict = {
"prompt.example.21": "Βοηθήστε με να γράψω ένα σενάριο μετεγκατάστασης",
"prompt.example.22": "Εφαρμογή προσωρινής αποθήκευσης για αυτό το τελικό σημείο",
"prompt.example.23": "Προσθήκη σελιδοποίησης σε αυτήν τη λίστα",
"prompt.example.24": "Δημιουργία εντολής CLI για",
"prompt.example.24": "Δημιουργία εντολής CLI για...",
"prompt.example.25": "Πώς λειτουργούν οι μεταβλητές περιβάλλοντος εδώ;",
"prompt.popover.emptyResults": "Δεν υπάρχουν αποτελέσματα",
"prompt.popover.emptyCommands": "Δεν ταιριάζουν εντολές",
@@ -406,7 +406,7 @@ export const dict = {
"dialog.directory.parent": "Γονικός",
"dialog.directory.readError": "Δεν είναι δυνατή η ανάγνωση αυτού του φακέλου",
"app.server.unreachable": "Δεν ήταν δυνατή η πρόσβαση στο {{server}}",
"app.server.retrying": "Αυτόματη προσπάθεια επανάληψης",
"app.server.retrying": "Αυτόματη προσπάθεια επανάληψης...",
"app.server.otherServers": "Άλλοι διακομιστές",
"dialog.server.title": "Διακομιστές",
"dialog.server.description": "Αλλαγή σε ποιον διακομιστή OpenCode συνδέεται αυτή η εφαρμογή.",
@@ -416,7 +416,7 @@ export const dict = {
"dialog.server.add.url": "Διεύθυνση διακομιστή",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Δεν ήταν δυνατή η σύνδεση με τον διακομιστή",
"dialog.server.add.checking": "Έλεγχος",
"dialog.server.add.checking": "Έλεγχος...",
"dialog.server.add.button": "Προσθήκη διακομιστή",
"dialog.server.add.name": "Όνομα διακομιστή (προαιρετικό)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -441,26 +441,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL διακομιστής",
"wsl.server.retryStart": "Επανάληψη έναρξης",
"wsl.server.updating": "Ενημέρωση",
"wsl.server.updating": "Ενημέρωση...",
"wsl.onboarding.step.distro": "Επιλογή διανομής",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Έλεγχος WSL",
"wsl.onboarding.checkingRuntime": "Έλεγχος WSL...",
"wsl.onboarding.restartRequired": "Windows χρειάζεται επανεκκίνηση για να ολοκληρωθεί η εγκατάσταση του WSL.",
"wsl.onboarding.ready": "WSL είναι έτοιμο.",
"wsl.onboarding.required": "WSL απαιτείται για να συνεχίσετε.",
"wsl.onboarding.checkingDistros": "Έλεγχος διανομών",
"wsl.onboarding.installingDistro": "Εγκατάσταση του {{distro}}",
"wsl.onboarding.checkingDistro": "Έλεγχος {{distro}}",
"wsl.onboarding.listingDistros": "Διανομές λίστας",
"wsl.onboarding.checkingDistros": "Έλεγχος διανομών...",
"wsl.onboarding.installingDistro": "Εγκατάσταση του {{distro}}...",
"wsl.onboarding.checkingDistro": "Έλεγχος {{distro}}...",
"wsl.onboarding.listingDistros": "Διανομές λίστας...",
"wsl.onboarding.distroReady": "{{distro}} είναι έτοιμο.",
"wsl.onboarding.distroNotInstalled": "{{distro}} δεν έχει εγκατασταθεί ακόμα.",
"wsl.onboarding.openDistroOnce": "Ανοίξτε το {{distro}} μία φορά για να ολοκληρώσετε τη ρύθμιση.",
"wsl.onboarding.finishingDistro": "Ολοκλήρωση της ρύθμισης για το {{distro}}.",
"wsl.onboarding.pickDistro": "Επιλέξτε μια διανομή ή εγκαταστήστε μία παρακάτω.",
"wsl.onboarding.checkingOpencode": "Έλεγχος OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Έλεγχος OpenCode σε {{distro}}",
"wsl.onboarding.updatingOpencode": "Ενημέρωση OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Ενημέρωση OpenCode σε {{distro}}",
"wsl.onboarding.checkingOpencode": "Έλεγχος OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Έλεγχος OpenCode σε {{distro}}...",
"wsl.onboarding.updatingOpencode": "Ενημέρωση OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Ενημέρωση OpenCode σε {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Ενημέρωση OpenCode σε {{distro}}.",
"wsl.onboarding.updateOpencode": "Ενημέρωση OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode είναι έτοιμο στο {{distro}}.",
@@ -469,11 +469,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Εγκατάσταση OpenCode",
"wsl.onboarding.chooseDistroFirst": "Επιλέξτε πρώτα μια διανομή.",
"wsl.onboarding.loadFailed": "Απέτυχε η φόρτωση της κατάστασης WSL.",
"wsl.onboarding.loading": "Φόρτωση",
"wsl.onboarding.loading": "Φόρτωση...",
"wsl.onboarding.installedDistros": "Εγκατεστημένες διανομές",
"wsl.onboarding.checkAgain": "Έλεγχος ξανά",
"wsl.onboarding.distroStatus.ready": "Έτοιμο",
"wsl.onboarding.distroStatus.checking": "Έλεγχος",
"wsl.onboarding.distroStatus.checking": "Έλεγχος...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode δεν έχει εγκατασταθεί",
"wsl.onboarding.distroStatus.missingTools": "Λείπει το bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Δεν υποστηρίζεται · Χρήση WSL 2",
@@ -492,7 +492,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Όλες οι εγκατεστημένες διανομές έχουν ήδη προστεθεί.",
"wsl.onboarding.noDistros": "Δεν έχουν εντοπιστεί ακόμη διανομές.",
"wsl.onboarding.install": "Εγκατάσταση",
"wsl.onboarding.installing": "Εγκατάσταση",
"wsl.onboarding.installing": "Εγκατάσταση...",
"wsl.onboarding.installDistro": "Εγκατάσταση διανομής",
"wsl.onboarding.searchDistros": "Αναζήτηση διανομών",
"wsl.onboarding.wsl2Required": "WSL 2 απαιτείται.",
@@ -504,7 +504,7 @@ export const dict = {
"wsl.onboarding.unknown": "άγνωστο",
"wsl.onboarding.desktopVersion": "επιτραπέζιος υπολογιστής {{version}}",
"wsl.onboarding.versionMismatch": "Η εγκατεστημένη έκδοση δεν ταιριάζει με την έκδοση της εφαρμογής για υπολογιστές.",
"wsl.onboarding.adding": "Προσθήκη",
"wsl.onboarding.adding": "Προσθήκη...",
"dialog.project.edit.title": "Επεξεργασία έργου",
"dialog.project.edit.name": "Όνομα",
"dialog.project.edit.icon": "Εικονίδιο",
@@ -611,7 +611,7 @@ export const dict = {
"error.page.action.report": "Αναφορά σφάλματος",
"error.page.action.reported": "Αναφέρθηκε σφάλμα",
"error.page.action.exportLogs": "Εξαγωγή αρχείων καταγραφής",
"error.page.action.checking": "Έλεγχος",
"error.page.action.checking": "Έλεγχος...",
"error.page.action.checkUpdates": "Έλεγχος για ενημερώσεις",
"error.page.action.updateTo": "Ενημέρωση σε {{version}}",
"error.page.circular": "[Circular]",
@@ -693,12 +693,12 @@ export const dict = {
"session.review.filesChanged": "Τα αρχεία άλλαξαν {{count}}",
"session.review.change.one": "Αλλαγή",
"session.review.change.other": "Αλλαγές",
"session.review.loadingChanges": "Φόρτωση αλλαγών",
"session.review.loadingChanges": "Φόρτωση αλλαγών...",
"session.review.empty": "Δεν υπάρχουν ακόμη αλλαγές σε αυτήν τη συνεδρία",
"session.review.noVcs": "Δεν εντοπίστηκε σύστημα ελέγχου έκδοσης Git, οι αλλαγές δεν εμφανίζονται",
"session.review.noVcs.createGit.title": "Δημιουργία αποθετηρίου Git",
"session.review.noVcs.createGit.description": "Παρακολούθηση, έλεγχος και αναίρεση αλλαγών σε αυτό το έργο",
"session.review.noVcs.createGit.actionLoading": "Δημιουργία αποθετηρίου Git",
"session.review.noVcs.createGit.actionLoading": "Δημιουργία αποθετηρίου Git...",
"session.review.noVcs.createGit.action": "Δημιουργία αποθετηρίου Git",
"session.review.noSnapshot":
"Η παρακολούθηση στιγμιότυπου είναι απενεργοποιημένη στις ρυθμίσεις παραμέτρων, επομένως οι αλλαγές περιόδου λειτουργίας δεν είναι διαθέσιμες",
@@ -710,9 +710,9 @@ export const dict = {
"session.files.empty": "Δεν υπάρχουν αρχεία",
"session.files.binaryContent": "Δυαδικό αρχείο (το περιεχόμενο δεν μπορεί να εμφανιστεί)",
"session.messages.renderEarlier": "Απόδοση προηγούμενων μηνυμάτων",
"session.messages.loadingEarlier": "Φόρτωση προηγούμενων μηνυμάτων",
"session.messages.loadingEarlier": "Φόρτωση προηγούμενων μηνυμάτων...",
"session.messages.loadEarlier": "Φόρτωση προηγούμενων μηνυμάτων",
"session.messages.loading": "Φόρτωση μηνυμάτων",
"session.messages.loading": "Φόρτωση μηνυμάτων...",
"session.messages.jumpToLatest": "Μετάβαση στο πιο πρόσφατο",
"session.context.addToContext": "Προσθήκη {{selection}} στο περιβάλλον",
"session.todo.title": "Εκκρεμότητες",
@@ -773,8 +773,8 @@ export const dict = {
"common.copied": "Αντιγράφηκε",
"lsp.tooltip.none": "Δεν υπάρχουν LSP διακομιστές",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Φόρτωση προτροπής",
"terminal.loading": "Φόρτωση τερματικού",
"prompt.loading": "Φόρτωση προτροπής...",
"terminal.loading": "Φόρτωση τερματικού...",
"terminal.title": "Τερματικό",
"terminal.title.numbered": "Τερματικό {{number}}",
"terminal.close": "Κλείσιμο τερματικού",
@@ -943,9 +943,9 @@ export const dict = {
"settings.updates.row.check.title": "Έλεγχος για ενημερώσεις",
"settings.updates.row.check.description": "Ελέγξτε μη αυτόματα για ενημερώσεις και εγκαταστήστε εάν είναι διαθέσιμες",
"settings.updates.action.checkNow": "Έλεγχος τώρα",
"settings.updates.action.checking": "Έλεγχος",
"settings.updates.action.downloading": "Λήψη",
"settings.updates.action.installing": "Εγκατάσταση",
"settings.updates.action.checking": "Έλεγχος...",
"settings.updates.action.downloading": "Λήψη...",
"settings.updates.action.installing": "Εγκατάσταση...",
"settings.updates.toast.latest.title": "Είστε ενημερωμένοι",
"settings.updates.toast.latest.description": "Εκτελείτε την πιο πρόσφατη έκδοση του OpenCode.",
"sound.option.none": "Κανένα",
@@ -1093,7 +1093,7 @@ export const dict = {
"workspace.reset.success.title": "Επαναφορά χώρου εργασίας",
"workspace.reset.success.description": "Ο χώρος εργασίας αντιστοιχεί πλέον στον προεπιλεγμένο κλάδο.",
"workspace.error.stillPreparing": "Ο χώρος εργασίας προετοιμάζεται ακόμη",
"workspace.status.checking": "Έλεγχος για μη συγχωνευμένες αλλαγές",
"workspace.status.checking": "Έλεγχος για μη συγχωνευμένες αλλαγές...",
"workspace.status.error": "Δεν είναι δυνατή η επαλήθευση της κατάστασης git.",
"workspace.status.clean": "Δεν εντοπίστηκαν μη συγχωνευμένες αλλαγές.",
"workspace.status.dirty": "Εντοπίστηκαν μη συγχωνευμένες αλλαγές σε αυτόν τον χώρο εργασίας.",
+81 -82
View File
@@ -23,7 +23,7 @@ export const dict = {
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Permissions",
"command.category.workspace": "Worktree",
"command.category.workspace": "Workspace",
"command.category.settings": "Settings",
"command.logs.export": "Export logs",
@@ -88,8 +88,8 @@ export const dict = {
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Auto-accept permissions",
"command.permissions.autoaccept.disable": "Stop auto-accepting permissions",
"command.workspace.toggle": "Toggle worktrees",
"command.workspace.toggle.description": "Enable or disable multiple worktrees in the sidebar",
"command.workspace.toggle": "Toggle workspaces",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
"command.session.undo": "Undo",
"command.session.undo.description": "Undo the last message",
"command.session.redo": "Redo",
@@ -143,8 +143,8 @@ export const dict = {
"provider.connect.method.apiKey": "API key",
"provider.connect.method.browser": "Browser",
"provider.connect.method.headless": "Headless",
"provider.connect.status.inProgress": "Authorization in progress",
"provider.connect.status.waiting": "Waiting for authorization",
"provider.connect.status.inProgress": "Authorization in progress...",
"provider.connect.status.waiting": "Waiting for authorization...",
"provider.connect.status.failed": "Authorization failed: {{error}}",
"provider.connect.apiKey.description":
"Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.",
@@ -241,7 +241,7 @@ export const dict = {
"common.goBack": "Navigate back",
"common.goForward": "Navigate forward",
"common.loading": "Loading",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel",
"common.clear": "Clear",
"common.open": "Open",
@@ -250,13 +250,13 @@ export const dict = {
"common.continue": "Continue",
"common.submit": "Submit",
"common.save": "Save",
"common.saving": "Saving",
"common.saving": "Saving...",
"common.default": "Default",
"common.attachment": "attachment",
"prompt.placeholder.shell": "Enter shell command {{example}}",
"prompt.placeholder.normal": 'Ask anything "{{example}}"',
"prompt.placeholder.simple": "Ask anything",
"prompt.placeholder.shell": "Enter shell command... {{example}}",
"prompt.placeholder.normal": 'Ask anything... "{{example}}"',
"prompt.placeholder.simple": "Ask anything...",
"prompt.placeholder.summarizeComments": "Summarize comments…",
"prompt.placeholder.summarizeComment": "Summarize comment…",
"prompt.mode.shell": "Shell",
@@ -277,7 +277,7 @@ export const dict = {
"prompt.example.10": "Generate API documentation",
"prompt.example.11": "Optimize database queries",
"prompt.example.12": "Add input validation",
"prompt.example.13": "Create a new component for",
"prompt.example.13": "Create a new component for...",
"prompt.example.14": "How do I deploy this project?",
"prompt.example.15": "Review my code for best practices",
"prompt.example.16": "Add error handling to this function",
@@ -288,7 +288,7 @@ export const dict = {
"prompt.example.21": "Help me write a migration script",
"prompt.example.22": "Implement caching for this endpoint",
"prompt.example.23": "Add pagination to this list",
"prompt.example.24": "Create a CLI command for",
"prompt.example.24": "Create a CLI command for...",
"prompt.example.25": "How do environment variables work here?",
"prompt.popover.emptyResults": "No matching results",
@@ -348,7 +348,7 @@ export const dict = {
"dialog.directory.readError": "Unable to read this folder",
"app.server.unreachable": "Could not reach {{server}}",
"app.server.retrying": "Retrying automatically",
"app.server.retrying": "Retrying automatically...",
"app.server.otherServers": "Other servers",
"dialog.server.title": "Servers",
@@ -359,7 +359,7 @@ export const dict = {
"dialog.server.add.url": "Server address",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Could not connect to server",
"dialog.server.add.checking": "Checking",
"dialog.server.add.checking": "Checking...",
"dialog.server.add.button": "Add server",
"dialog.server.add.name": "Server name (optional)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -387,26 +387,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Retry start",
"wsl.server.updating": "Updating",
"wsl.server.updating": "Updating...",
"wsl.onboarding.step.distro": "Choose distro",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Checking WSL",
"wsl.onboarding.checkingRuntime": "Checking WSL...",
"wsl.onboarding.restartRequired": "Windows needs a restart to finish installing WSL.",
"wsl.onboarding.ready": "WSL is ready.",
"wsl.onboarding.required": "WSL is required to continue.",
"wsl.onboarding.checkingDistros": "Checking distros",
"wsl.onboarding.installingDistro": "Installing {{distro}}",
"wsl.onboarding.checkingDistro": "Checking {{distro}}",
"wsl.onboarding.listingDistros": "Listing distros",
"wsl.onboarding.checkingDistros": "Checking distros...",
"wsl.onboarding.installingDistro": "Installing {{distro}}...",
"wsl.onboarding.checkingDistro": "Checking {{distro}}...",
"wsl.onboarding.listingDistros": "Listing distros...",
"wsl.onboarding.distroReady": "{{distro}} is ready.",
"wsl.onboarding.distroNotInstalled": "{{distro}} is not installed yet.",
"wsl.onboarding.openDistroOnce": "Open {{distro}} once to finish setup.",
"wsl.onboarding.finishingDistro": "Finishing setup for {{distro}}.",
"wsl.onboarding.pickDistro": "Pick a distro or install one below.",
"wsl.onboarding.checkingOpencode": "Checking OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Checking OpenCode in {{distro}}",
"wsl.onboarding.updatingOpencode": "Updating OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Updating OpenCode in {{distro}}",
"wsl.onboarding.checkingOpencode": "Checking OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Checking OpenCode in {{distro}}...",
"wsl.onboarding.updatingOpencode": "Updating OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Updating OpenCode in {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Update OpenCode in {{distro}}.",
"wsl.onboarding.updateOpencode": "Update OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode is ready in {{distro}}.",
@@ -415,11 +415,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Install OpenCode",
"wsl.onboarding.chooseDistroFirst": "Choose a distro first.",
"wsl.onboarding.loadFailed": "Failed to load WSL state.",
"wsl.onboarding.loading": "Loading",
"wsl.onboarding.loading": "Loading...",
"wsl.onboarding.installedDistros": "Installed distros",
"wsl.onboarding.checkAgain": "Check again",
"wsl.onboarding.distroStatus.ready": "Ready",
"wsl.onboarding.distroStatus.checking": "Checking",
"wsl.onboarding.distroStatus.checking": "Checking...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode not installed",
"wsl.onboarding.distroStatus.missingTools": "Missing bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Unsupported · Use WSL 2",
@@ -437,7 +437,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "All installed distros are already added.",
"wsl.onboarding.noDistros": "No distros detected yet.",
"wsl.onboarding.install": "Install",
"wsl.onboarding.installing": "Installing",
"wsl.onboarding.installing": "Installing...",
"wsl.onboarding.installDistro": "Install distro",
"wsl.onboarding.searchDistros": "Search distros",
"wsl.onboarding.wsl2Required": "WSL 2 is required.",
@@ -449,7 +449,7 @@ export const dict = {
"wsl.onboarding.unknown": "unknown",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Installed version does not match the desktop app version.",
"wsl.onboarding.adding": "Adding",
"wsl.onboarding.adding": "Adding...",
"server.row.incompatible":
"This server is running OpenCode {{version}}, which isn't compatible with this app. Upgrade it to OpenCode V2 to continue.",
@@ -462,9 +462,9 @@ export const dict = {
"dialog.project.edit.icon.recommended": "Recommended: 128x128px",
"dialog.project.edit.color": "Color",
"dialog.project.edit.color.select": "Select {{color}} color",
"dialog.project.edit.worktree.startup": "Worktree startup script",
"dialog.project.edit.worktree.startup": "Workspace startup script",
"dialog.project.edit.worktree.startup.description":
"Runs after creating a new worktree. Use $OPENCODE_WORKTREE_BASE for the base worktree and $OPENCODE_WORKTREE_PATH for the new worktree.",
"Runs after creating a new workspace (worktree). Use $OPENCODE_WORKTREE_BASE for the base worktree and $OPENCODE_WORKTREE_PATH for the new worktree.",
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
"dialog.releaseNotes.action.getStarted": "Get started",
@@ -533,9 +533,9 @@ export const dict = {
"toast.theme.title": "Theme switched",
"toast.scheme.title": "Color scheme",
"toast.workspace.enabled.title": "Worktrees enabled",
"toast.workspace.enabled.title": "Workspaces enabled",
"toast.workspace.enabled.description": "Multiple worktrees are now shown in the sidebar",
"toast.workspace.disabled.title": "Worktrees disabled",
"toast.workspace.disabled.title": "Workspaces disabled",
"toast.workspace.disabled.description": "Only the main worktree is shown in the sidebar",
"toast.permissions.autoaccept.on.title": "Auto-accepting permissions",
@@ -586,7 +586,7 @@ export const dict = {
"error.page.action.report": "Report Error",
"error.page.action.reported": "Error Reported",
"error.page.action.exportLogs": "Export Logs",
"error.page.action.checking": "Checking",
"error.page.action.checking": "Checking...",
"error.page.action.checkUpdates": "Check for updates",
"error.page.action.updateTo": "Update to {{version}}",
"error.page.circular": "[Circular]",
@@ -694,7 +694,6 @@ export const dict = {
"session.queue.remove": "Remove",
"session.queue.reorder": "Reorder queued prompt",
"session.queue.attachments": "+ attachments",
"session.timeline.working": "Working",
"session.timeline.notice.finished": "{{actor}} finished",
"session.timeline.notice.failed": "{{actor}} failed",
"session.timeline.notice.cancelled": "{{actor}} cancelled",
@@ -702,13 +701,13 @@ export const dict = {
"session.review.filesChanged": "Files Changed {{count}}",
"session.review.change.one": "Change",
"session.review.change.other": "Changes",
"session.review.loadingChanges": "Loading changes",
"session.review.loadingChanges": "Loading changes...",
"session.review.wrapLines": "Wrap lines",
"session.review.empty": "No changes in this session yet",
"session.review.noVcs": "No Git Version Control System detected, changes not displayed",
"session.review.noVcs.createGit.title": "Create a Git repository",
"session.review.noVcs.createGit.description": "Track, review, and undo changes in this project",
"session.review.noVcs.createGit.actionLoading": "Creating Git repository",
"session.review.noVcs.createGit.actionLoading": "Creating Git repository...",
"session.review.noVcs.createGit.action": "Create Git repository",
"session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable",
"session.review.noChanges": "No changes",
@@ -721,9 +720,9 @@ export const dict = {
"session.files.binaryContent": "Binary file (content cannot be displayed)",
"session.messages.renderEarlier": "Render earlier messages",
"session.messages.loadingEarlier": "Loading earlier messages",
"session.messages.loadingEarlier": "Loading earlier messages...",
"session.messages.loadEarlier": "Load earlier messages",
"session.messages.loading": "Loading messages",
"session.messages.loading": "Loading messages...",
"session.messages.jumpToLatest": "Jump to latest",
"session.context.addToContext": "Add {{selection}} to context",
@@ -745,11 +744,10 @@ export const dict = {
"session.new.worktree.mainWithBranch": "Main branch ({{branch}})",
"session.new.worktree.create": "Create new worktree",
"session.new.worktree.creating": "Creating worktree",
"session.new.worktree.draftPlaceholder": "Draft a follow-up while the worktree is being prepared…",
"session.new.workspace.runIn": "Run session in",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Local repository",
"session.new.workspace.existing": "Worktree…",
"session.new.workspace.existing": "Workspace…",
"session.new.git.none": "No Git",
"session.new.lastModified": "Last modified",
@@ -793,8 +791,8 @@ export const dict = {
"lsp.tooltip.none": "No LSP servers",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Loading prompt",
"terminal.loading": "Loading terminal",
"prompt.loading": "Loading prompt...",
"terminal.loading": "Loading terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Close terminal",
@@ -850,8 +848,8 @@ export const dict = {
"sidebar.nav.projectsAndSessions": "Projects and sessions",
"sidebar.settings": "Settings",
"sidebar.help": "Help",
"sidebar.workspaces.enable": "Enable worktrees",
"sidebar.workspaces.disable": "Disable worktrees",
"sidebar.workspaces.enable": "Enable workspaces",
"sidebar.workspaces.disable": "Disable workspaces",
"sidebar.gettingStarted.title": "Getting started",
"sidebar.gettingStarted.line1": "OpenCode includes free models so you can start immediately.",
"sidebar.gettingStarted.line2": "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
@@ -984,7 +982,8 @@ export const dict = {
"settings.general.row.showProjectIcon.title": "Project icon",
"settings.general.row.showProjectIcon.description": "Show the project icon in the session header",
"settings.general.row.mobileTitlebarBottom.title": "Bottom navigation",
"settings.general.row.mobileTitlebarBottom.description": "Place the title bar at the bottom of the screen on mobile",
"settings.general.row.mobileTitlebarBottom.description":
"Place the title bar at the bottom of the screen on mobile",
"settings.general.row.mobileDiffWrap.description":
"Wrap long lines in mobile diffs instead of scrolling horizontally",
"settings.general.row.showCustomAgents.title": "Show agent",
@@ -1024,9 +1023,9 @@ export const dict = {
"settings.updates.row.check.title": "Check for updates",
"settings.updates.row.check.description": "Manually check for updates and install if available",
"settings.updates.action.checkNow": "Check now",
"settings.updates.action.checking": "Checking",
"settings.updates.action.downloading": "Downloading",
"settings.updates.action.installing": "Installing",
"settings.updates.action.checking": "Checking...",
"settings.updates.action.downloading": "Downloading...",
"settings.updates.action.installing": "Installing...",
"settings.updates.toast.latest.title": "You're up to date",
"settings.updates.toast.latest.description": "You're running the latest version of OpenCode.",
"sound.option.none": "None",
@@ -1172,7 +1171,7 @@ export const dict = {
"session.delete.confirm": 'Delete session "{{name}}"?',
"session.delete.button": "Delete session",
"workspace.new": "New worktree",
"workspace.new": "New workspace",
"common.viewAll": "View all",
"session.new.workspace.local.tooltip": "Use current checkout",
"session.new.workspace.new.tooltip": "Create isolated checkout",
@@ -1180,66 +1179,66 @@ export const dict = {
"session.new.workspace.createFrom": "Create from branch",
"session.new.workspace.branch.search.placeholder": "Search branches",
"session.new.workspace.trigger.tooltip": "Select where to run session",
"session.new.workspace.search.placeholder": "Search worktrees",
"settings.tab.workspaces": "Worktrees",
"session.new.workspace.search.placeholder": "Search workspaces",
"settings.tab.workspaces": "Workspaces",
"settings.workspaces.filter.all": "All projects",
"settings.workspaces.empty": "No worktrees",
"settings.workspaces.count.one": "{{count}} worktree",
"settings.workspaces.count.other": "{{count}} worktrees",
"settings.workspaces.empty": "No workspaces",
"settings.workspaces.count.one": "{{count}} workspace",
"settings.workspaces.count.other": "{{count}} workspaces",
"settings.workspaces.sessions.one": "{{count}} session in {{project}}",
"settings.workspaces.sessions.other": "{{count}} sessions in {{project}}",
"settings.workspaces.lastActiveSession": "Last active session",
"settings.workspaces.deleteAll": "Delete all worktrees",
"settings.workspaces.deleteAll.confirm": "Delete all {{count}} worktrees?",
"settings.workspaces.deleteAll": "Delete all workspaces",
"settings.workspaces.deleteAll.confirm": "Delete all {{count}} workspaces?",
"settings.workspaces.delete.warning":
"The worktree directory and branch will be permanently removed, including any unmerged changes shown below.",
"The workspace directory and branch will be permanently removed, including any unmerged changes shown below.",
"settings.workspaces.deleteAll.warning":
"The {{count}} selected worktrees in {{project}} will be permanently removed only if each is clean, inactive, and has no linked sessions.",
"settings.workspaces.delete.blocked.active": "The active worktree cannot be deleted.",
"The {{count}} selected workspaces in {{project}} will be permanently removed only if each is clean, inactive, and has no linked sessions.",
"settings.workspaces.delete.blocked.active": "The active workspace cannot be deleted.",
"settings.workspaces.delete.blocked.linked":
"Linked sessions will remain, but their working directory will be permanently removed.",
"settings.workspaces.default.title": "Default environment",
"settings.workspaces.default.description": "Choose where new sessions start",
"settings.workspaces.default.lastUsed": "Last used per project",
"settings.workspaces.default.local": "Local directory",
"settings.workspaces.default.new": "New worktree",
"workspace.move.title": "Move to worktree",
"settings.workspaces.default.new": "New workspace",
"workspace.move.title": "Move to workspace",
"workspace.move.menu.title": "Move session to",
"workspace.move.failed": "Failed to move session",
"workspace.lifecycle.creating": "Creating worktree",
"workspace.lifecycle.created": "Worktree created",
"workspace.lifecycle.creating": "Creating workspace",
"workspace.lifecycle.created": "Workspace created",
"workspace.lifecycle.starting": "Starting session",
"workspace.onboarding.title": "Isolate sessions with worktrees",
"workspace.onboarding.title": "Isolate sessions with workspaces",
"workspace.onboarding.description": "Each gets its own checkout, so nothing interferes with your local repository",
"workspace.lifecycle.moving": "Moving to worktree",
"workspace.lifecycle.set": "Worktree set",
"workspace.lifecycle.moving": "Moving to workspace",
"workspace.lifecycle.set": "Workspace set",
"session.summary.title": "Session details",
"session.summary.noBranch": "No branch",
"session.summary.basedOn": "Based on {{branch}}",
"workspace.type.local": "local",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Failed to create worktree",
"workspace.delete.failed.title": "Failed to delete worktree",
"workspace.resetting.title": "Resetting worktree",
"workspace.create.failed.title": "Failed to create workspace",
"workspace.delete.failed.title": "Failed to delete workspace",
"workspace.resetting.title": "Resetting workspace",
"workspace.resetting.description": "This may take a minute.",
"workspace.reset.failed.title": "Failed to reset worktree",
"workspace.reset.success.title": "Worktree reset",
"workspace.reset.success.description": "Worktree now matches the default branch.",
"workspace.error.stillPreparing": "Worktree is still preparing",
"workspace.status.checking": "Checking for unmerged changes",
"workspace.reset.failed.title": "Failed to reset workspace",
"workspace.reset.success.title": "Workspace reset",
"workspace.reset.success.description": "Workspace now matches the default branch.",
"workspace.error.stillPreparing": "Workspace is still preparing",
"workspace.status.checking": "Checking for unmerged changes...",
"workspace.status.error": "Unable to verify git status.",
"workspace.status.clean": "No unmerged changes detected.",
"workspace.status.dirty": "Unmerged changes detected in this worktree.",
"workspace.delete.title": "Delete worktree",
"workspace.delete.confirm": 'Delete worktree "{{name}}"?',
"workspace.delete.button": "Delete worktree",
"workspace.reset.title": "Reset worktree",
"workspace.reset.confirm": 'Reset worktree "{{name}}"?',
"workspace.reset.button": "Reset worktree",
"workspace.status.dirty": "Unmerged changes detected in this workspace.",
"workspace.delete.title": "Delete workspace",
"workspace.delete.confirm": 'Delete workspace "{{name}}"?',
"workspace.delete.button": "Delete workspace",
"workspace.reset.title": "Reset workspace",
"workspace.reset.confirm": 'Reset workspace "{{name}}"?',
"workspace.reset.button": "Reset workspace",
"workspace.reset.archived.none": "No active sessions will be archived.",
"workspace.reset.archived.one": "1 session will be archived.",
"workspace.reset.archived.many": "{{count}} sessions will be archived.",
"workspace.reset.note": "This will reset the worktree to match the default branch.",
"workspace.reset.note": "This will reset the workspace to match the default branch.",
}
export default dict
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Ir",
"desktop.menu.window": "Ventana",
"desktop.menu.help": "Ayuda",
"desktop.menu.checkForUpdates": "Buscar actualizaciones",
"desktop.menu.checkForUpdates": "Buscar actualizaciones...",
"desktop.menu.settings": "Ajustes",
"desktop.menu.reloadWebview": "Volver a cargar la vista web",
"desktop.menu.restart": "Reiniciar",
"desktop.menu.exportLogs": "Exportar registros",
"desktop.menu.exportLogs": "Exportar registros...",
"desktop.menu.newSession": "Nueva sesión",
"desktop.menu.openProject": "Abrir proyecto",
"desktop.menu.openProject": "Abrir proyecto...",
"desktop.menu.newWindow": "Nueva ventana",
"desktop.menu.closeWindow": "Cerrar ventana",
"desktop.menu.undo": "Deshacer",
@@ -233,8 +233,8 @@ export const dict = {
"provider.connect.method.apiKey": "Clave API",
"provider.connect.method.browser": "Navegador",
"provider.connect.method.headless": "Sin navegador",
"provider.connect.status.inProgress": "Autorización en progreso",
"provider.connect.status.waiting": "Esperando autorización",
"provider.connect.status.inProgress": "Autorización en progreso...",
"provider.connect.status.waiting": "Esperando autorización...",
"provider.connect.status.failed": "Autorización fallida: {{error}}",
"provider.connect.apiKey.description":
"Introduce tu clave API de {{provider}} para conectar tu cuenta y usar modelos de {{provider}} en OpenCode.",
@@ -332,7 +332,7 @@ export const dict = {
"common.goBack": "Volver",
"common.goForward": "Avanzar",
"common.loading": "Cargando",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Cancelar",
"common.clear": "Limpiar",
"common.connect": "Conectar",
@@ -340,13 +340,13 @@ export const dict = {
"common.continue": "Continuar",
"common.submit": "Enviar",
"common.save": "Guardar",
"common.saving": "Guardando",
"common.saving": "Guardando...",
"common.default": "Predeterminado",
"common.attachment": "adjunto",
"prompt.placeholder.shell": "Introduce comando de shell {{example}}",
"prompt.placeholder.normal": 'Pregunta cualquier cosa "{{example}}"',
"prompt.placeholder.simple": "Pregunta cualquier cosa",
"prompt.placeholder.shell": "Introduce comando de shell... {{example}}",
"prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"',
"prompt.placeholder.simple": "Pregunta cualquier cosa...",
"prompt.placeholder.summarizeComments": "Resumir comentarios…",
"prompt.placeholder.summarizeComment": "Resumir comentario…",
"prompt.mode.shell": "Shell",
@@ -367,7 +367,7 @@ export const dict = {
"prompt.example.10": "Generar documentación de API",
"prompt.example.11": "Optimizar consultas a la base de datos",
"prompt.example.12": "Añadir validación de entrada",
"prompt.example.13": "Crear un nuevo componente para",
"prompt.example.13": "Crear un nuevo componente para...",
"prompt.example.14": "¿Cómo despliego este proyecto?",
"prompt.example.15": "Revisar si mi código sigue las prácticas recomendadas",
"prompt.example.16": "Añadir gestión de errores a esta función",
@@ -378,7 +378,7 @@ export const dict = {
"prompt.example.21": "Ayúdame a escribir un script de migración",
"prompt.example.22": "Implementar caché para este punto de conexión",
"prompt.example.23": "Añadir paginación a esta lista",
"prompt.example.24": "Crear un comando CLI para",
"prompt.example.24": "Crear un comando CLI para...",
"prompt.example.25": "¿Cómo funcionan las variables de entorno aquí?",
"prompt.popover.emptyResults": "Sin resultados coincidentes",
@@ -446,7 +446,7 @@ export const dict = {
"dialog.server.add.url": "URL del servidor",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "No se pudo conectar al servidor",
"dialog.server.add.checking": "Comprobando",
"dialog.server.add.checking": "Comprobando...",
"dialog.server.add.button": "Añadir servidor",
"dialog.server.add.name": "Nombre del servidor (opcional)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -471,26 +471,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Reintentar el inicio",
"wsl.server.updating": "Actualizando",
"wsl.server.updating": "Actualizando...",
"wsl.onboarding.step.distro": "Elegir distribución",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Comprobando WSL",
"wsl.onboarding.checkingRuntime": "Comprobando WSL...",
"wsl.onboarding.restartRequired": "Es necesario reiniciar Windows para terminar de instalar WSL.",
"wsl.onboarding.ready": "WSL está listo.",
"wsl.onboarding.required": "Se necesita WSL para continuar.",
"wsl.onboarding.checkingDistros": "Comprobando distribuciones",
"wsl.onboarding.installingDistro": "Instalando {{distro}}",
"wsl.onboarding.checkingDistro": "Comprobando {{distro}}",
"wsl.onboarding.listingDistros": "Listando distribuciones",
"wsl.onboarding.checkingDistros": "Comprobando distribuciones...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Comprobando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuciones...",
"wsl.onboarding.distroReady": "La distribución {{distro}} está lista.",
"wsl.onboarding.distroNotInstalled": "La distribución {{distro}} aún no está instalada.",
"wsl.onboarding.openDistroOnce": "Abre {{distro}} una vez para completar la configuración.",
"wsl.onboarding.finishingDistro": "Completando la configuración de {{distro}}.",
"wsl.onboarding.pickDistro": "Elige una distribución o instala una abajo.",
"wsl.onboarding.checkingOpencode": "Comprobando OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}",
"wsl.onboarding.updatingOpencode": "Actualizando OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}",
"wsl.onboarding.checkingOpencode": "Comprobando OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}...",
"wsl.onboarding.updatingOpencode": "Actualizando OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Actualiza OpenCode en {{distro}}.",
"wsl.onboarding.updateOpencode": "Actualizar OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode está listo en {{distro}}.",
@@ -499,11 +499,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Instalar OpenCode",
"wsl.onboarding.chooseDistroFirst": "Elige primero una distribución.",
"wsl.onboarding.loadFailed": "No se pudo cargar el estado de WSL.",
"wsl.onboarding.loading": "Cargando",
"wsl.onboarding.loading": "Cargando...",
"wsl.onboarding.installedDistros": "Distribuciones instaladas",
"wsl.onboarding.checkAgain": "Comprobar de nuevo",
"wsl.onboarding.distroStatus.ready": "Lista",
"wsl.onboarding.distroStatus.checking": "Comprobando",
"wsl.onboarding.distroStatus.checking": "Comprobando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no está instalado",
"wsl.onboarding.distroStatus.missingTools": "Faltan bash y curl",
"wsl.onboarding.distroStatus.unsupported": "No compatible · Usa WSL 2",
@@ -522,7 +522,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Ya se han añadido todas las distribuciones instaladas.",
"wsl.onboarding.noDistros": "Aún no se han detectado distribuciones.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribución",
"wsl.onboarding.searchDistros": "Buscar distribuciones",
"wsl.onboarding.wsl2Required": "Se necesita WSL 2.",
@@ -534,7 +534,7 @@ export const dict = {
"wsl.onboarding.unknown": "desconocida",
"wsl.onboarding.desktopVersion": "escritorio {{version}}",
"wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.",
"wsl.onboarding.adding": "Añadiendo",
"wsl.onboarding.adding": "Añadiendo...",
"dialog.project.edit.title": "Editar proyecto",
"dialog.project.edit.name": "Nombre",
@@ -658,7 +658,7 @@ export const dict = {
"error.page.action.report": "Informar del error",
"error.page.action.reported": "Error notificado",
"error.page.action.exportLogs": "Exportar registros",
"error.page.action.checking": "Comprobando",
"error.page.action.checking": "Comprobando...",
"error.page.action.checkUpdates": "Buscar actualizaciones",
"error.page.action.updateTo": "Actualizar a {{version}}",
"error.page.report.prefix": "Informa de este error al equipo de OpenCode",
@@ -736,7 +736,7 @@ export const dict = {
"session.review.filesChanged": "Archivos modificados: {{count}}",
"session.review.change.one": "Cambio",
"session.review.change.other": "Cambios",
"session.review.loadingChanges": "Cargando cambios",
"session.review.loadingChanges": "Cargando cambios...",
"session.review.empty": "Aún no hay cambios en esta sesión",
"session.review.noVcs": "No se detectó ningún sistema de control de versiones Git; no se muestran los cambios",
"session.review.noSnapshot":
@@ -751,9 +751,9 @@ export const dict = {
"session.files.binaryContent": "Archivo binario (no se puede mostrar el contenido)",
"session.messages.renderEarlier": "Mostrar mensajes anteriores",
"session.messages.loadingEarlier": "Cargando mensajes anteriores",
"session.messages.loadingEarlier": "Cargando mensajes anteriores...",
"session.messages.loadEarlier": "Cargar mensajes anteriores",
"session.messages.loading": "Cargando mensajes",
"session.messages.loading": "Cargando mensajes...",
"session.messages.jumpToLatest": "Ir al mensaje más reciente",
"session.context.addToContext": "Añadir {{selection}} al contexto",
@@ -801,8 +801,8 @@ export const dict = {
"lsp.tooltip.none": "Sin servidores LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Cargando prompt",
"terminal.loading": "Cargando terminal",
"prompt.loading": "Cargando prompt...",
"terminal.loading": "Cargando terminal...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Cerrar terminal",
@@ -926,9 +926,9 @@ export const dict = {
"settings.updates.row.check.title": "Buscar actualizaciones",
"settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna",
"settings.updates.action.checkNow": "Buscar ahora",
"settings.updates.action.checking": "Buscando",
"settings.updates.action.downloading": "Descargando",
"settings.updates.action.installing": "Instalando",
"settings.updates.action.checking": "Buscando...",
"settings.updates.action.downloading": "Descargando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Estás al día",
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
"sound.option.none": "Ninguno",
@@ -1085,7 +1085,7 @@ export const dict = {
"workspace.reset.success.title": "Espacio de trabajo restablecido",
"workspace.reset.success.description": "El espacio de trabajo ahora coincide con la rama predeterminada.",
"workspace.error.stillPreparing": "El espacio de trabajo aún se está preparando",
"workspace.status.checking": "Comprobando cambios no fusionados",
"workspace.status.checking": "Comprobando cambios no fusionados...",
"workspace.status.error": "No se pudo verificar el estado de Git.",
"workspace.status.clean": "No se detectaron cambios no fusionados.",
"workspace.status.dirty": "Cambios no fusionados detectados en este espacio de trabajo.",
@@ -1115,12 +1115,12 @@ export const dict = {
"settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.",
"app.server.unreachable": "No se pudo conectar con {{server}}",
"app.server.retrying": "Reintentando automáticamente",
"app.server.retrying": "Reintentando automáticamente...",
"app.server.otherServers": "Otros servidores",
"dialog.server.add.passwordPlaceholder": "contraseña",
"session.review.noVcs.createGit.title": "Crear repositorio Git",
"session.review.noVcs.createGit.description": "Rastrea, revisa y deshaz cambios en este proyecto",
"session.review.noVcs.createGit.actionLoading": "Creando repositorio Git",
"session.review.noVcs.createGit.actionLoading": "Creando repositorio Git...",
"session.review.noVcs.createGit.action": "Crear repositorio Git",
"session.todo.progress": "{{done}} de {{total}} tareas completadas",
"session.question.progress": "{{current}} de {{total}} preguntas",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Mine",
"desktop.menu.window": "Aken",
"desktop.menu.help": "Abi",
"desktop.menu.checkForUpdates": "Otsige värskendusi",
"desktop.menu.checkForUpdates": "Otsige värskendusi...",
"desktop.menu.settings": "Seaded",
"desktop.menu.reloadWebview": "Laadige Webview uuesti",
"desktop.menu.restart": "Taaskäivitage",
"desktop.menu.exportLogs": "Ekspordi logid",
"desktop.menu.exportLogs": "Ekspordi logid...",
"desktop.menu.newSession": "Uus sessioon",
"desktop.menu.openProject": "Ava projekt",
"desktop.menu.openProject": "Ava projekt...",
"desktop.menu.newWindow": "Uus aken",
"desktop.menu.closeWindow": "Sule aken",
"desktop.menu.undo": "Võta tagasi",
@@ -212,8 +212,8 @@ export const dict = {
"provider.connect.method.apiKey": "API klahv",
"provider.connect.method.browser": "Brauser",
"provider.connect.method.headless": "Peata",
"provider.connect.status.inProgress": "Autoriseerimine on pooleli",
"provider.connect.status.waiting": "Autoriseerimise ootel",
"provider.connect.status.inProgress": "Autoriseerimine on pooleli...",
"provider.connect.status.waiting": "Autoriseerimise ootel...",
"provider.connect.status.failed": "Autoriseerimine ebaõnnestus: {{error}}",
"provider.connect.apiKey.description":
"Sisestage {{provider}} API võti, et ühendada konto ja kasutada {{provider}} mudeleid OpenCode'is.",
@@ -306,7 +306,7 @@ export const dict = {
"common.goBack": "Navigeerige tagasi",
"common.goForward": "Liikuge edasi",
"common.loading": "Laadimine",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Tühista",
"common.clear": "Selge",
"common.open": "Avatud",
@@ -315,12 +315,12 @@ export const dict = {
"common.continue": "Jätka",
"common.submit": "Esita",
"common.save": "Salvesta",
"common.saving": "Salvestamine",
"common.saving": "Salvestamine...",
"common.default": "Vaikimisi",
"common.attachment": "manus",
"prompt.placeholder.shell": "Sisestage shellikäsk {{example}}",
"prompt.placeholder.normal": 'Küsige mida tahes "{{example}}"',
"prompt.placeholder.simple": "Küsi midagi",
"prompt.placeholder.shell": "Sisestage shellikäsk... {{example}}",
"prompt.placeholder.normal": 'Küsige mida tahes... "{{example}}"',
"prompt.placeholder.simple": "Küsi midagi...",
"prompt.placeholder.summarizeComments": "Tee kommentaaridest kokkuvõte…",
"prompt.placeholder.summarizeComment": "Kommentaari kokkuvõte…",
"prompt.mode.shell": "Shell",
@@ -340,7 +340,7 @@ export const dict = {
"prompt.example.10": "Loo API dokumentatsioon",
"prompt.example.11": "Optimeerige andmebaasi päringuid",
"prompt.example.12": "Lisage sisendi valideerimine",
"prompt.example.13": "Looge uus komponent ",
"prompt.example.13": "Looge uus komponent ...",
"prompt.example.14": "Kuidas seda projekti juurutada?",
"prompt.example.15": "Heade tavade saamiseks vaadake üle minu kood",
"prompt.example.16": "Lisage sellele funktsioonile veakäsitlus",
@@ -351,7 +351,7 @@ export const dict = {
"prompt.example.21": "Aidake mul migratsiooniskripti kirjutada",
"prompt.example.22": "Rakendage selle lõpp-punkti vahemälu",
"prompt.example.23": "Lisage sellele loendile lehekülgede arv",
"prompt.example.24": "Looge CLI käsk",
"prompt.example.24": "Looge CLI käsk...",
"prompt.example.25": "Kuidas keskkonnamuutujad siin töötavad?",
"prompt.popover.emptyResults": "Vastavaid tulemusi pole",
"prompt.popover.emptyCommands": "Ühtegi sobivat käsku pole",
@@ -403,7 +403,7 @@ export const dict = {
"dialog.directory.parent": "Lapsevanem",
"dialog.directory.readError": "Seda kausta ei saa lugeda",
"app.server.unreachable": "Ei saanud ühendust {{server}}",
"app.server.retrying": "Automaatne uuesti proovimine",
"app.server.retrying": "Automaatne uuesti proovimine...",
"app.server.otherServers": "Muud serverid",
"dialog.server.title": "Serverid",
"dialog.server.description": "Lülitage, millise OpenCode serveriga see rakendus ühenduse loob.",
@@ -413,7 +413,7 @@ export const dict = {
"dialog.server.add.url": "Serveri aadress",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Serveriga ei õnnestunud ühendust luua",
"dialog.server.add.checking": "Kontrollimine",
"dialog.server.add.checking": "Kontrollimine...",
"dialog.server.add.button": "Lisa server",
"dialog.server.add.name": "Serveri nimi (valikuline)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -438,26 +438,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Proovi uuesti alustada",
"wsl.server.updating": "Värskendamine",
"wsl.server.updating": "Värskendamine...",
"wsl.onboarding.step.distro": "Valige distro",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL kontrollimine",
"wsl.onboarding.checkingRuntime": "WSL kontrollimine...",
"wsl.onboarding.restartRequired": "Windows vajab installimise WSL lõpetamiseks taaskäivitamist.",
"wsl.onboarding.ready": "WSL on valmis.",
"wsl.onboarding.required": "Jätkamiseks on vaja WSL.",
"wsl.onboarding.checkingDistros": "Distrode kontrollimine",
"wsl.onboarding.installingDistro": "{{distro}} installimine",
"wsl.onboarding.checkingDistro": "{{distro}} kontrollimine",
"wsl.onboarding.listingDistros": "Distrode loend",
"wsl.onboarding.checkingDistros": "Distrode kontrollimine...",
"wsl.onboarding.installingDistro": "{{distro}} installimine...",
"wsl.onboarding.checkingDistro": "{{distro}} kontrollimine...",
"wsl.onboarding.listingDistros": "Distrode loend...",
"wsl.onboarding.distroReady": "{{distro}} on valmis.",
"wsl.onboarding.distroNotInstalled": "{{distro}} pole veel installitud.",
"wsl.onboarding.openDistroOnce": "Seadistamise lõpetamiseks avage üks kord {{distro}}.",
"wsl.onboarding.finishingDistro": "Seadistuse lõpetamine {{distro}} jaoks.",
"wsl.onboarding.pickDistro": "Valige distro või installige see allpool.",
"wsl.onboarding.checkingOpencode": "OpenCode kontrollimine",
"wsl.onboarding.checkingOpencodeIn": "OpenCode kontrollimine asukohas {{distro}}",
"wsl.onboarding.updatingOpencode": "OpenCode värskendamine",
"wsl.onboarding.updatingOpencodeIn": "OpenCode värskendamine asukohas {{distro}}",
"wsl.onboarding.checkingOpencode": "OpenCode kontrollimine...",
"wsl.onboarding.checkingOpencodeIn": "OpenCode kontrollimine asukohas {{distro}}...",
"wsl.onboarding.updatingOpencode": "OpenCode värskendamine...",
"wsl.onboarding.updatingOpencodeIn": "OpenCode värskendamine asukohas {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Värskenda OpenCode jaotises {{distro}}.",
"wsl.onboarding.updateOpencode": "Värskenda OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode on valmis {{distro}}.",
@@ -466,11 +466,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Installige OpenCode",
"wsl.onboarding.chooseDistroFirst": "Valige esmalt distro.",
"wsl.onboarding.loadFailed": "Oleku WSL laadimine ebaõnnestus.",
"wsl.onboarding.loading": "Laadimine",
"wsl.onboarding.loading": "Laadimine...",
"wsl.onboarding.installedDistros": "Installitud distrood",
"wsl.onboarding.checkAgain": "Kontrollige uuesti",
"wsl.onboarding.distroStatus.ready": "Valmis",
"wsl.onboarding.distroStatus.checking": "Kontrollimine",
"wsl.onboarding.distroStatus.checking": "Kontrollimine...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode pole installitud",
"wsl.onboarding.distroStatus.missingTools": "Puuduvad bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Toetamata · Kasutage WSL 2",
@@ -488,7 +488,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Kõik installitud distrood on juba lisatud.",
"wsl.onboarding.noDistros": "Ühtegi distrosid pole veel tuvastatud.",
"wsl.onboarding.install": "Installige",
"wsl.onboarding.installing": "Installimine",
"wsl.onboarding.installing": "Installimine...",
"wsl.onboarding.installDistro": "Installige distro",
"wsl.onboarding.searchDistros": "Otsige distrosid",
"wsl.onboarding.wsl2Required": "WSL 2 on nõutav.",
@@ -500,7 +500,7 @@ export const dict = {
"wsl.onboarding.unknown": "teadmata",
"wsl.onboarding.desktopVersion": "töölaud {{version}}",
"wsl.onboarding.versionMismatch": "Installitud versioon ei ühti töölauarakenduse versiooniga.",
"wsl.onboarding.adding": "Lisamine",
"wsl.onboarding.adding": "Lisamine...",
"dialog.project.edit.title": "Redigeeri projekti",
"dialog.project.edit.name": "Nimi",
"dialog.project.edit.icon": "Ikoon",
@@ -605,7 +605,7 @@ export const dict = {
"error.page.action.report": "Teata veast",
"error.page.action.reported": "Teatatud viga",
"error.page.action.exportLogs": "Ekspordi logid",
"error.page.action.checking": "Kontrollimine",
"error.page.action.checking": "Kontrollimine...",
"error.page.action.checkUpdates": "Kontrollige värskendusi",
"error.page.action.updateTo": "Värskenda väärtusele {{version}}",
"error.page.circular": "[Ringkiri]",
@@ -682,12 +682,12 @@ export const dict = {
"session.review.filesChanged": "Muudetud failid {{count}}",
"session.review.change.one": "Muuda",
"session.review.change.other": "Muudatused",
"session.review.loadingChanges": "Muudatuste laadimine",
"session.review.loadingChanges": "Muudatuste laadimine...",
"session.review.empty": "Sellel seansil pole veel muudatusi",
"session.review.noVcs": "Ei tuvastatud Git Versioonikontrollisüsteemi, muudatusi ei kuvata",
"session.review.noVcs.createGit.title": "Loo Git hoidla",
"session.review.noVcs.createGit.description": "Selle projekti muudatuste jälgimine, ülevaatamine ja tagasivõtmine",
"session.review.noVcs.createGit.actionLoading": "Git hoidla loomine",
"session.review.noVcs.createGit.actionLoading": "Git hoidla loomine...",
"session.review.noVcs.createGit.action": "Loo Git hoidla",
"session.review.noSnapshot":
"Hetketõmmise jälgimine on konfiguratsioonis keelatud, seega pole seansi muudatused saadaval",
@@ -699,9 +699,9 @@ export const dict = {
"session.files.empty": "Faile pole",
"session.files.binaryContent": "Binaarfail (sisu ei saa kuvada)",
"session.messages.renderEarlier": "Renderda varasemaid sõnumeid",
"session.messages.loadingEarlier": "Varasemate sõnumite laadimine",
"session.messages.loadingEarlier": "Varasemate sõnumite laadimine...",
"session.messages.loadEarlier": "Laadige varasemad sõnumid",
"session.messages.loading": "Sõnumite laadimine",
"session.messages.loading": "Sõnumite laadimine...",
"session.messages.jumpToLatest": "Hüppa uusimale",
"session.context.addToContext": "Lisage konteksti {{selection}}",
"session.todo.title": "Ülesanded",
@@ -762,8 +762,8 @@ export const dict = {
"common.copied": "Kopeeritud",
"lsp.tooltip.none": "LSP serverit pole",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Viipa laadimine",
"terminal.loading": "Terminali laadimine",
"prompt.loading": "Viipa laadimine...",
"terminal.loading": "Terminali laadimine...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Sulgege terminal",
@@ -925,9 +925,9 @@ export const dict = {
"settings.updates.row.check.title": "Kontrollige värskendusi",
"settings.updates.row.check.description": "Kontrollige värskendusi käsitsi ja installige, kui need on saadaval",
"settings.updates.action.checkNow": "Kontrollige kohe",
"settings.updates.action.checking": "Kontrollimine",
"settings.updates.action.downloading": "Allalaadimine",
"settings.updates.action.installing": "Installimine",
"settings.updates.action.checking": "Kontrollimine...",
"settings.updates.action.downloading": "Allalaadimine...",
"settings.updates.action.installing": "Installimine...",
"settings.updates.toast.latest.title": "Oled kursis",
"settings.updates.toast.latest.description": "Kasutate rakenduse OpenCode uusimat versiooni.",
"sound.option.none": "Mitte ühtegi",
@@ -1073,7 +1073,7 @@ export const dict = {
"workspace.reset.success.title": "Tööruumi lähtestamine",
"workspace.reset.success.description": "Tööala vastab nüüd vaikeharule.",
"workspace.error.stillPreparing": "Tööala on veel ettevalmistamisel",
"workspace.status.checking": "Ühendamata muudatuste otsimine",
"workspace.status.checking": "Ühendamata muudatuste otsimine...",
"workspace.status.error": "git-olekut ei saa kinnitada.",
"workspace.status.clean": "Ühtegi ühendamata muudatust ei tuvastatud.",
"workspace.status.dirty": "Selles tööruumis tuvastati ühendamata muudatused.",
+39 -39
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "برو",
"desktop.menu.window": "پنجره",
"desktop.menu.help": "کمک کنید",
"desktop.menu.checkForUpdates": "بررسی به روز رسانی",
"desktop.menu.checkForUpdates": "بررسی به روز رسانی...",
"desktop.menu.settings": "تنظیمات",
"desktop.menu.reloadWebview": "بارگذاری مجدد Webview",
"desktop.menu.restart": "راه اندازی مجدد",
"desktop.menu.exportLogs": "صادرات گزارش‌ها",
"desktop.menu.exportLogs": "صادرات گزارش‌ها...",
"desktop.menu.newSession": "جلسه جدید",
"desktop.menu.openProject": "باز کردن پروژه ",
"desktop.menu.openProject": "باز کردن پروژه ...",
"desktop.menu.newWindow": "پنجره جدید",
"desktop.menu.closeWindow": "بستن پنجره",
"desktop.menu.undo": "واگرد",
@@ -213,8 +213,8 @@ export const dict = {
"provider.connect.method.apiKey": "کلید API",
"provider.connect.method.browser": "مرورگر",
"provider.connect.method.headless": "بدون رابط گرافیکی",
"provider.connect.status.inProgress": "مجوز در حال انجام است",
"provider.connect.status.waiting": "در انتظار مجوز",
"provider.connect.status.inProgress": "مجوز در حال انجام است...",
"provider.connect.status.waiting": "در انتظار مجوز...",
"provider.connect.status.failed": "مجوز انجام نشد: {{error}}",
"provider.connect.apiKey.description":
"کلید API سرویس {{provider}} را وارد کنید تا حساب شما متصل شود و بتوانید از مدل‌های {{provider}} در OpenCode استفاده کنید.",
@@ -307,7 +307,7 @@ export const dict = {
"common.goBack": "به عقب پیمایش کنید",
"common.goForward": "به جلو حرکت کنید",
"common.loading": "در حال بارگذاری",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "لغو کنید",
"common.clear": "پاک کردن",
"common.open": "باز کنید",
@@ -316,12 +316,12 @@ export const dict = {
"common.continue": "ادامه دهید",
"common.submit": "ارسال کنید",
"common.save": "ذخیره کنید",
"common.saving": "در حال ذخیره",
"common.saving": "در حال ذخیره...",
"common.default": "پیش فرض",
"common.attachment": "پیوست",
"prompt.placeholder.shell": "دستور پوسته را وارد کنید {{example}}",
"prompt.placeholder.normal": 'هر چیزی بپرسید "{{example}}"',
"prompt.placeholder.simple": "هر چیزی بپرس",
"prompt.placeholder.shell": "دستور پوسته را وارد کنید... {{example}}",
"prompt.placeholder.normal": 'هر چیزی بپرسید... "{{example}}"',
"prompt.placeholder.simple": "هر چیزی بپرس...",
"prompt.placeholder.summarizeComments": "خلاصه نظرات…",
"prompt.placeholder.summarizeComment": "خلاصه نظر…",
"prompt.mode.shell": "پوسته",
@@ -341,7 +341,7 @@ export const dict = {
"prompt.example.10": "مستندات API را ایجاد کنید",
"prompt.example.11": "بهینه سازی پرس و جوهای پایگاه داده",
"prompt.example.12": "افزودن اعتبار سنجی ورودی",
"prompt.example.13": "ایجاد یک جزء جدید برای ",
"prompt.example.13": "ایجاد یک جزء جدید برای ...",
"prompt.example.14": "چگونه این پروژه را مستقر کنم؟",
"prompt.example.15": "کد من را برای بهترین شیوه ها مرور کنید",
"prompt.example.16": "مدیریت خطا را به این تابع اضافه کنید",
@@ -352,7 +352,7 @@ export const dict = {
"prompt.example.21": "به من کمک کنید تا یک اسکریپت مهاجرت بنویسم",
"prompt.example.22": "کش را برای این نقطه پایانی اجرا کنید",
"prompt.example.23": "صفحه بندی را به این لیست اضافه کنید",
"prompt.example.24": "ایجاد یک دستور CLI برای ",
"prompt.example.24": "ایجاد یک دستور CLI برای ...",
"prompt.example.25": "متغیرهای محیطی در اینجا چگونه کار می کنند؟",
"prompt.popover.emptyResults": "نتایج منطبقی وجود ندارد",
"prompt.popover.emptyCommands": "بدون دستور منطبق",
@@ -404,7 +404,7 @@ export const dict = {
"dialog.directory.parent": "پدر و مادر",
"dialog.directory.readError": "خواندن این پوشه ممکن نیست",
"app.server.unreachable": "نمی توان به {{server}} رسید",
"app.server.retrying": "تلاش مجدد به صورت خودکار",
"app.server.retrying": "تلاش مجدد به صورت خودکار...",
"app.server.otherServers": "سرورهای دیگر",
"dialog.server.title": "سرورها",
"dialog.server.description": "این برنامه به کدام سرور OpenCode متصل است، تغییر دهید.",
@@ -414,7 +414,7 @@ export const dict = {
"dialog.server.add.url": "آدرس سرور",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "نمی توان به سرور متصل شد",
"dialog.server.add.checking": "در حال بررسی",
"dialog.server.add.checking": "در حال بررسی...",
"dialog.server.add.button": "سرور اضافه کنید",
"dialog.server.add.name": "نام سرور (اختیاری)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -439,26 +439,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "سرور WSL",
"wsl.server.retryStart": "شروع مجدد امتحان کنید",
"wsl.server.updating": "در حال به روز رسانی",
"wsl.server.updating": "در حال به روز رسانی...",
"wsl.onboarding.step.distro": "توزیع را انتخاب کنید",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "در حال بررسی WSL",
"wsl.onboarding.checkingRuntime": "در حال بررسی WSL...",
"wsl.onboarding.restartRequired": "Windows برای اتمام نصب WSL به راه اندازی مجدد نیاز دارد.",
"wsl.onboarding.ready": "WSL آماده است.",
"wsl.onboarding.required": "WSL برای ادامه مورد نیاز است.",
"wsl.onboarding.checkingDistros": "در حال بررسی توزیع‌ها",
"wsl.onboarding.installingDistro": "در حال نصب {{distro}}",
"wsl.onboarding.checkingDistro": "در حال بررسی {{distro}}",
"wsl.onboarding.listingDistros": "فهرست توزیع‌ها",
"wsl.onboarding.checkingDistros": "در حال بررسی توزیع‌ها...",
"wsl.onboarding.installingDistro": "در حال نصب {{distro}}...",
"wsl.onboarding.checkingDistro": "در حال بررسی {{distro}}...",
"wsl.onboarding.listingDistros": "فهرست توزیع‌ها...",
"wsl.onboarding.distroReady": "{{distro}} آماده است.",
"wsl.onboarding.distroNotInstalled": "{{distro}} هنوز نصب نشده است.",
"wsl.onboarding.openDistroOnce": "{{distro}} را یک بار باز کنید تا راه‌اندازی تمام شود.",
"wsl.onboarding.finishingDistro": "تکمیل راه اندازی برای {{distro}}.",
"wsl.onboarding.pickDistro": "یک توزیع را انتخاب کنید یا یکی را در زیر نصب کنید.",
"wsl.onboarding.checkingOpencode": "در حال بررسی OpenCode",
"wsl.onboarding.checkingOpencodeIn": "بررسی OpenCode در {{distro}}",
"wsl.onboarding.updatingOpencode": "در حال به روز رسانی OpenCode",
"wsl.onboarding.updatingOpencodeIn": "به روز رسانی OpenCode در {{distro}}",
"wsl.onboarding.checkingOpencode": "در حال بررسی OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "بررسی OpenCode در {{distro}}...",
"wsl.onboarding.updatingOpencode": "در حال به روز رسانی OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "به روز رسانی OpenCode در {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "OpenCode را در {{distro}} به روز کنید.",
"wsl.onboarding.updateOpencode": "OpenCode را به روز کنید",
"wsl.onboarding.opencodeReadyIn": "OpenCode در {{distro}} آماده است.",
@@ -467,11 +467,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "OpenCode را نصب کنید",
"wsl.onboarding.chooseDistroFirst": "ابتدا یک توزیع را انتخاب کنید.",
"wsl.onboarding.loadFailed": "وضعیت WSL بارگیری نشد.",
"wsl.onboarding.loading": "در حال بارگیری",
"wsl.onboarding.loading": "در حال بارگیری...",
"wsl.onboarding.installedDistros": "توزیع های نصب شده",
"wsl.onboarding.checkAgain": "دوباره چک کنید",
"wsl.onboarding.distroStatus.ready": "آماده است",
"wsl.onboarding.distroStatus.checking": "در حال بررسی",
"wsl.onboarding.distroStatus.checking": "در حال بررسی...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode نصب نشده است",
"wsl.onboarding.distroStatus.missingTools": "bash، curl وجود ندارد",
"wsl.onboarding.distroStatus.unsupported": "پشتیبانی نشده · از WSL 2 استفاده کنید",
@@ -490,7 +490,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "همه توزیع‌های نصب‌شده قبلاً اضافه شده‌اند.",
"wsl.onboarding.noDistros": "هنوز هیچ توزیعی شناسایی نشده است.",
"wsl.onboarding.install": "نصب کنید",
"wsl.onboarding.installing": "در حال نصب",
"wsl.onboarding.installing": "در حال نصب...",
"wsl.onboarding.installDistro": "توزیع را نصب کنید",
"wsl.onboarding.searchDistros": "جستجوی توزیع ها",
"wsl.onboarding.wsl2Required": "WSL 2 مورد نیاز است.",
@@ -502,7 +502,7 @@ export const dict = {
"wsl.onboarding.unknown": "ناشناخته",
"wsl.onboarding.desktopVersion": "دسکتاپ {{version}}",
"wsl.onboarding.versionMismatch": "نسخه نصب شده با نسخه برنامه دسکتاپ مطابقت ندارد.",
"wsl.onboarding.adding": "در حال افزودن",
"wsl.onboarding.adding": "در حال افزودن...",
"dialog.project.edit.title": "ویرایش پروژه",
"dialog.project.edit.name": "نام",
"dialog.project.edit.icon": "نماد",
@@ -607,7 +607,7 @@ export const dict = {
"error.page.action.report": "گزارش خطا",
"error.page.action.reported": "خطا گزارش شده است",
"error.page.action.exportLogs": "صادرات سیاهههای مربوط",
"error.page.action.checking": "در حال بررسی",
"error.page.action.checking": "در حال بررسی...",
"error.page.action.checkUpdates": "به روز رسانی را بررسی کنید",
"error.page.action.updateTo": "به روز رسانی به {{version}}",
"error.page.circular": "[دایره]",
@@ -685,12 +685,12 @@ export const dict = {
"session.review.filesChanged": "فایل ها تغییر کردند {{count}}",
"session.review.change.one": "تغییر دهید",
"session.review.change.other": "تغییرات",
"session.review.loadingChanges": "در حال بارگیری تغییرات",
"session.review.loadingChanges": "در حال بارگیری تغییرات...",
"session.review.empty": "هنوز هیچ تغییری در این جلسه وجود ندارد",
"session.review.noVcs": "هیچ سیستم کنترل نسخه Git شناسایی نشد، تغییرات نمایش داده نمی شود",
"session.review.noVcs.createGit.title": "یک مخزن Git ایجاد کنید",
"session.review.noVcs.createGit.description": "ردیابی، بررسی، و لغو تغییرات در این پروژه",
"session.review.noVcs.createGit.actionLoading": "ایجاد مخزن Git",
"session.review.noVcs.createGit.actionLoading": "ایجاد مخزن Git...",
"session.review.noVcs.createGit.action": "مخزن Git ایجاد کنید",
"session.review.noSnapshot": "ردیابی عکس فوری در پیکربندی غیرفعال است، بنابراین تغییرات جلسه در دسترس نیست",
"session.review.noChanges": "بدون تغییر",
@@ -701,9 +701,9 @@ export const dict = {
"session.files.empty": "بدون فایل",
"session.files.binaryContent": "فایل باینری (محتوا قابل نمایش نیست)",
"session.messages.renderEarlier": "ارسال پیام های قبلی",
"session.messages.loadingEarlier": "در حال بارگیری پیام های قبلی",
"session.messages.loadingEarlier": "در حال بارگیری پیام های قبلی...",
"session.messages.loadEarlier": "بارگیری پیام های قبلی",
"session.messages.loading": "در حال بارگیری پیام ها",
"session.messages.loading": "در حال بارگیری پیام ها...",
"session.messages.jumpToLatest": "پرش به آخرین",
"session.context.addToContext": "{{selection}} را به متن اضافه کنید",
"session.todo.title": "کارهای انجام‌دادنی",
@@ -764,8 +764,8 @@ export const dict = {
"common.copied": "کپی شد",
"lsp.tooltip.none": "بدون سرور LSP",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "در حال بارگیری درخواست",
"terminal.loading": "در حال بارگیری ترمینال",
"prompt.loading": "در حال بارگیری درخواست...",
"terminal.loading": "در حال بارگیری ترمینال...",
"terminal.title": "ترمینال",
"terminal.title.numbered": "ترمینال {{number}}",
"terminal.close": "ترمینال را ببندید",
@@ -926,9 +926,9 @@ export const dict = {
"settings.updates.row.check.title": "به روز رسانی را بررسی کنید",
"settings.updates.row.check.description": "به‌روزرسانی‌ها را به‌صورت دستی بررسی کنید و در صورت وجود نصب کنید",
"settings.updates.action.checkNow": "اکنون بررسی کنید",
"settings.updates.action.checking": "در حال بررسی",
"settings.updates.action.downloading": "در حال دانلود",
"settings.updates.action.installing": "در حال نصب",
"settings.updates.action.checking": "در حال بررسی...",
"settings.updates.action.downloading": "در حال دانلود...",
"settings.updates.action.installing": "در حال نصب...",
"settings.updates.toast.latest.title": "شما به روز هستید",
"settings.updates.toast.latest.description": "شما آخرین نسخه OpenCode را اجرا می کنید.",
"sound.option.none": "هیچ کدام",
@@ -1075,7 +1075,7 @@ export const dict = {
"workspace.reset.success.title": "بازنشانی فضای کاری",
"workspace.reset.success.description": "اکنون فضای کاری با شاخه پیش‌فرض مطابقت دارد.",
"workspace.error.stillPreparing": "فضای کاری همچنان در حال آماده سازی است",
"workspace.status.checking": "در حال بررسی تغییرات ادغام نشده",
"workspace.status.checking": "در حال بررسی تغییرات ادغام نشده...",
"workspace.status.error": "تأیید وضعیت git ممکن نیست.",
"workspace.status.clean": "هیچ تغییر ادغام نشده شناسایی نشد.",
"workspace.status.dirty": "تغییرات ادغام نشده در این فضای کاری شناسایی شد.",
+39 -39
View File
@@ -120,8 +120,8 @@ export const dict = {
"provider.connect.method.apiKey": "API-avain",
"provider.connect.method.browser": "Selain",
"provider.connect.method.headless": "Päätön",
"provider.connect.status.inProgress": "Valtuutus käynnissä",
"provider.connect.status.waiting": "Odotetaan valtuutusta",
"provider.connect.status.inProgress": "Valtuutus käynnissä...",
"provider.connect.status.waiting": "Odotetaan valtuutusta...",
"provider.connect.status.failed": "Valtuutus epäonnistui: {{error}}",
"provider.connect.apiKey.description":
"Anna {{provider}}-API-avaimesi yhdistääksesi tilisi ja käyttääksesi {{provider}}-malleja OpenCodessa.",
@@ -214,7 +214,7 @@ export const dict = {
"common.goBack": "Siirry takaisin",
"common.goForward": "Siirry eteenpäin",
"common.loading": "Ladataan",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Peruuta",
"common.clear": "Tyhjennä",
"common.open": "Avaa",
@@ -223,12 +223,12 @@ export const dict = {
"common.continue": "Jatka",
"common.submit": "Lähetä",
"common.save": "Tallenna",
"common.saving": "Tallennetaan",
"common.saving": "Tallennetaan...",
"common.default": "Oletus",
"common.attachment": "liite",
"prompt.placeholder.shell": "Anna shell-komento {{example}}",
"prompt.placeholder.normal": 'Kysy mitä tahansa "{{example}}"',
"prompt.placeholder.simple": "Kysy mitä tahansa",
"prompt.placeholder.shell": "Anna shell-komento... {{example}}",
"prompt.placeholder.normal": 'Kysy mitä tahansa... "{{example}}"',
"prompt.placeholder.simple": "Kysy mitä tahansa...",
"prompt.placeholder.summarizeComments": "Tiivistä kommentit…",
"prompt.placeholder.summarizeComment": "Tiivistä kommentti…",
"prompt.mode.shell": "Shell",
@@ -248,7 +248,7 @@ export const dict = {
"prompt.example.10": "Luo API-dokumentaatio",
"prompt.example.11": "Optimoi tietokantakyselyt",
"prompt.example.12": "Lisää syötteen validointi",
"prompt.example.13": "Luo uusi komponentti",
"prompt.example.13": "Luo uusi komponentti...",
"prompt.example.14": "Kuinka otan tämän projektin käyttöön?",
"prompt.example.15": "Tarkista koodini parhaat käytännöt",
"prompt.example.16": "Lisää tähän toimintoon virheenkäsittely",
@@ -259,7 +259,7 @@ export const dict = {
"prompt.example.21": "Auta minua kirjoittamaan migraatioskripti",
"prompt.example.22": "Ota välimuisti käyttöön tälle päätepisteelle",
"prompt.example.23": "Lisää sivutus tähän luetteloon",
"prompt.example.24": "Luo CLI-komento",
"prompt.example.24": "Luo CLI-komento...",
"prompt.example.25": "Miten ympäristömuuttujat toimivat tässä?",
"prompt.popover.emptyResults": "Ei vastaavia tuloksia",
"prompt.popover.emptyCommands": "Ei vastaavia komentoja",
@@ -311,7 +311,7 @@ export const dict = {
"dialog.directory.parent": "Ylätaso",
"dialog.directory.readError": "Tätä kansiota ei voi lukea",
"app.server.unreachable": "Palvelimeen {{server}} ei saatu yhteyttä",
"app.server.retrying": "Yritetään automaattisesti uudelleen",
"app.server.retrying": "Yritetään automaattisesti uudelleen...",
"app.server.otherServers": "Muut palvelimet",
"dialog.server.title": "Palvelimet",
"dialog.server.description": "Vaihda OpenCode-palvelimeen, johon tämä sovellus muodostaa yhteyden.",
@@ -321,7 +321,7 @@ export const dict = {
"dialog.server.add.url": "Palvelimen osoite",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Ei voitu muodostaa yhteyttä palvelimeen",
"dialog.server.add.checking": "Tarkistetaan",
"dialog.server.add.checking": "Tarkistetaan...",
"dialog.server.add.button": "Lisää palvelin",
"dialog.server.add.name": "Palvelimen nimi (valinnainen)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -346,26 +346,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-palvelin",
"wsl.server.retryStart": "Yritä käynnistystä uudelleen",
"wsl.server.updating": "Päivitetään",
"wsl.server.updating": "Päivitetään...",
"wsl.onboarding.step.distro": "Valitse jakelu",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Tarkistetaan WSL",
"wsl.onboarding.checkingRuntime": "Tarkistetaan WSL...",
"wsl.onboarding.restartRequired": "Windows tarvitsee uudelleenkäynnistyksen WSL:n asennuksen viimeistelemiseksi.",
"wsl.onboarding.ready": "WSL on valmis.",
"wsl.onboarding.required": "WSL vaaditaan jatkamiseen.",
"wsl.onboarding.checkingDistros": "Tarkistetaan jakeluja",
"wsl.onboarding.installingDistro": "Asennetaan {{distro}}",
"wsl.onboarding.checkingDistro": "Tarkistetaan {{distro}}",
"wsl.onboarding.listingDistros": "Luetteloidaan jakeluja",
"wsl.onboarding.checkingDistros": "Tarkistetaan jakeluja...",
"wsl.onboarding.installingDistro": "Asennetaan {{distro}}...",
"wsl.onboarding.checkingDistro": "Tarkistetaan {{distro}}...",
"wsl.onboarding.listingDistros": "Luetteloidaan jakeluja...",
"wsl.onboarding.distroReady": "{{distro}} on valmis.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ei ole vielä asennettu.",
"wsl.onboarding.openDistroOnce": "Avaa {{distro}} kerran viimeistelläksesi asetukset.",
"wsl.onboarding.finishingDistro": "Viimeistellään jakelun {{distro}} asennusta.",
"wsl.onboarding.pickDistro": "Valitse jakelu tai asenna sellainen alta.",
"wsl.onboarding.checkingOpencode": "Tarkistetaan OpenCodea",
"wsl.onboarding.checkingOpencodeIn": "Tarkistetaan OpenCodea kohteessa {{distro}}",
"wsl.onboarding.updatingOpencode": "Päivitetään OpenCodea",
"wsl.onboarding.updatingOpencodeIn": "Päivitetään OpenCodea kohteessa {{distro}}",
"wsl.onboarding.checkingOpencode": "Tarkistetaan OpenCodea...",
"wsl.onboarding.checkingOpencodeIn": "Tarkistetaan OpenCodea kohteessa {{distro}}...",
"wsl.onboarding.updatingOpencode": "Päivitetään OpenCodea...",
"wsl.onboarding.updatingOpencodeIn": "Päivitetään OpenCodea kohteessa {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Päivitä OpenCode jakelussa {{distro}}.",
"wsl.onboarding.updateOpencode": "Päivitä OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode on valmis jakelussa {{distro}}.",
@@ -374,11 +374,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Asenna OpenCode",
"wsl.onboarding.chooseDistroFirst": "Valitse ensin jakelu.",
"wsl.onboarding.loadFailed": "WSL-tilan lataaminen epäonnistui.",
"wsl.onboarding.loading": "Ladataan",
"wsl.onboarding.loading": "Ladataan...",
"wsl.onboarding.installedDistros": "Asennetut jakelut",
"wsl.onboarding.checkAgain": "Tarkista uudelleen",
"wsl.onboarding.distroStatus.ready": "Valmis",
"wsl.onboarding.distroStatus.checking": "Tarkistetaan",
"wsl.onboarding.distroStatus.checking": "Tarkistetaan...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCodea ei ole asennettu",
"wsl.onboarding.distroStatus.missingTools": "Puuttuu bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ei tuettu · Käytä WSL 2:ta",
@@ -397,7 +397,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Kaikki asennetut jakelut on jo lisätty.",
"wsl.onboarding.noDistros": "Jakeluja ei ole vielä havaittu.",
"wsl.onboarding.install": "Asenna",
"wsl.onboarding.installing": "Asennetaan",
"wsl.onboarding.installing": "Asennetaan...",
"wsl.onboarding.installDistro": "Asenna jakelu",
"wsl.onboarding.searchDistros": "Etsi jakeluja",
"wsl.onboarding.wsl2Required": "WSL 2 vaaditaan.",
@@ -409,7 +409,7 @@ export const dict = {
"wsl.onboarding.unknown": "tuntematon",
"wsl.onboarding.desktopVersion": "Desktop {{version}}",
"wsl.onboarding.versionMismatch": "Asennettu versio ei vastaa työpöytäsovelluksen versiota.",
"wsl.onboarding.adding": "Lisätään",
"wsl.onboarding.adding": "Lisätään...",
"dialog.project.edit.title": "Muokkaa projektia",
"dialog.project.edit.name": "Nimi",
"dialog.project.edit.icon": "Kuvake",
@@ -519,7 +519,7 @@ export const dict = {
"error.page.action.report": "Ilmoita virheestä",
"error.page.action.reported": "Virhe raportoitu",
"error.page.action.exportLogs": "Vie lokit",
"error.page.action.checking": "Tarkistetaan",
"error.page.action.checking": "Tarkistetaan...",
"error.page.action.checkUpdates": "Tarkista päivitykset",
"error.page.action.updateTo": "Päivitä versioon {{version}}",
"error.page.circular": "[Kehäviittaus]",
@@ -598,12 +598,12 @@ export const dict = {
"session.review.filesChanged": "Muutettuja tiedostoja: {{count}}",
"session.review.change.one": "Muutos",
"session.review.change.other": "Muutokset",
"session.review.loadingChanges": "Ladataan muutoksia",
"session.review.loadingChanges": "Ladataan muutoksia...",
"session.review.empty": "Tässä istunnossa ei vielä muutoksia",
"session.review.noVcs": "Git-versionhallintaa ei havaittu, joten muutoksia ei näytetä",
"session.review.noVcs.createGit.title": "Luo Git-säilö",
"session.review.noVcs.createGit.description": "Seuraa, tarkista ja kumoa muutoksia tässä projektissa",
"session.review.noVcs.createGit.actionLoading": "Luodaan Git-säilöä",
"session.review.noVcs.createGit.actionLoading": "Luodaan Git-säilöä...",
"session.review.noVcs.createGit.action": "Luo Git-säilö",
"session.review.noSnapshot":
"Tilannevedosten seuranta on poistettu käytöstä määrityksessä, joten istunnon muutokset eivät ole käytettävissä",
@@ -615,9 +615,9 @@ export const dict = {
"session.files.empty": "Ei tiedostoja",
"session.files.binaryContent": "Binääritiedosto (sisältöä ei voi näyttää)",
"session.messages.renderEarlier": "Hahmonna aiemmat viestit",
"session.messages.loadingEarlier": "Ladataan aikaisempia viestejä",
"session.messages.loadingEarlier": "Ladataan aikaisempia viestejä...",
"session.messages.loadEarlier": "Lataa aikaisemmat viestit",
"session.messages.loading": "Ladataan viestejä",
"session.messages.loading": "Ladataan viestejä...",
"session.messages.jumpToLatest": "Siirry uusimpaan",
"session.context.addToContext": "Lisää {{selection}} kontekstiin",
"session.todo.title": "Tehtävät",
@@ -678,8 +678,8 @@ export const dict = {
"common.copied": "Kopioitu",
"lsp.tooltip.none": "Ei LSP-palvelimia",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Ladataan kehotetta",
"terminal.loading": "Ladataan terminaalia",
"prompt.loading": "Ladataan kehotetta...",
"terminal.loading": "Ladataan terminaalia...",
"terminal.title": "Terminaali",
"terminal.title.numbered": "Terminaali {{number}}",
"terminal.close": "Sulje terminaali",
@@ -845,9 +845,9 @@ export const dict = {
"settings.updates.row.check.title": "Tarkista päivitykset",
"settings.updates.row.check.description": "Tarkista päivitykset manuaalisesti ja asenna, jos niitä on saatavilla",
"settings.updates.action.checkNow": "Tarkista nyt",
"settings.updates.action.checking": "Tarkistetaan",
"settings.updates.action.downloading": "Ladataan",
"settings.updates.action.installing": "Asennetaan",
"settings.updates.action.checking": "Tarkistetaan...",
"settings.updates.action.downloading": "Ladataan...",
"settings.updates.action.installing": "Asennetaan...",
"settings.updates.toast.latest.title": "Olet ajan tasalla",
"settings.updates.toast.latest.description": "Käytät OpenCoden uusinta versiota.",
"sound.option.none": "Ei mitään",
@@ -996,7 +996,7 @@ export const dict = {
"workspace.reset.success.title": "Työtila palautettu",
"workspace.reset.success.description": "Työtila vastaa nyt oletushaaraa.",
"workspace.error.stillPreparing": "Työtilaa valmistellaan vielä",
"workspace.status.checking": "Tarkistetaan yhdistämättömiä muutoksia",
"workspace.status.checking": "Tarkistetaan yhdistämättömiä muutoksia...",
"workspace.status.error": "Gitin tilaa ei voi vahvistaa.",
"workspace.status.clean": "Yhdistämättömiä muutoksia ei havaittu.",
"workspace.status.dirty": "Tässä työtilassa havaittu yhdistämättömiä muutoksia.",
@@ -1017,13 +1017,13 @@ export const dict = {
"desktop.menu.go": "Siirry",
"desktop.menu.window": "Ikkuna",
"desktop.menu.help": "Ohje",
"desktop.menu.checkForUpdates": "Tarkista päivitykset",
"desktop.menu.checkForUpdates": "Tarkista päivitykset...",
"desktop.menu.settings": "Asetukset",
"desktop.menu.reloadWebview": "Lataa verkkonäkymä uudelleen",
"desktop.menu.restart": "Käynnistä uudelleen",
"desktop.menu.exportLogs": "Vie lokit",
"desktop.menu.exportLogs": "Vie lokit...",
"desktop.menu.newSession": "Uusi istunto",
"desktop.menu.openProject": "Avaa projekti",
"desktop.menu.openProject": "Avaa projekti...",
"desktop.menu.newWindow": "Uusi ikkuna",
"desktop.menu.closeWindow": "Sulje ikkuna",
"desktop.menu.undo": "Kumoa",
+42 -42
View File
@@ -6,13 +6,13 @@ export const dict = {
"desktop.menu.go": "Far",
"desktop.menu.window": "Vindeyga",
"desktop.menu.help": "Hjálp",
"desktop.menu.checkForUpdates": "Kanna fyri dagføringum",
"desktop.menu.checkForUpdates": "Kanna fyri dagføringum...",
"desktop.menu.settings": "Innstillingar",
"desktop.menu.reloadWebview": "Endurlesa vevvísing",
"desktop.menu.restart": "Endurbyrja",
"desktop.menu.exportLogs": "Útflyt loggar",
"desktop.menu.exportLogs": "Útflyt loggar...",
"desktop.menu.newSession": "Nýggj seta",
"desktop.menu.openProject": "Opna verkætlan",
"desktop.menu.openProject": "Opna verkætlan...",
"desktop.menu.newWindow": "Nýtt vindeyga",
"desktop.menu.closeWindow": "Lat vindeyga aftur",
"desktop.menu.undo": "Angra",
@@ -212,8 +212,8 @@ export const dict = {
"provider.connect.method.apiKey": "API lykil",
"provider.connect.method.browser": "Kagari",
"provider.connect.method.headless": "Høvdleysur",
"provider.connect.status.inProgress": "Heimild í gongd",
"provider.connect.status.waiting": "Bíða eftir heimild",
"provider.connect.status.inProgress": "Heimild í gongd...",
"provider.connect.status.waiting": "Bíða eftir heimild...",
"provider.connect.status.failed": "Heimild miseydnaðist: {{error}}",
"provider.connect.apiKey.description":
"Skriva {{provider}} API-lykilin fyri at knýta kontuna saman og brúka {{provider}}-modell í OpenCode.",
@@ -306,7 +306,7 @@ export const dict = {
"common.goBack": "Far aftur",
"common.goForward": "Far frameftir",
"common.loading": "Innlesing",
"common.loading.ellipsis": "",
"common.loading.ellipsis": "...",
"common.cancel": "Avlýs",
"common.clear": "Klára",
"common.open": "Opið",
@@ -315,14 +315,14 @@ export const dict = {
"common.continue": "Halt fram",
"common.submit": "Send inn",
"common.save": "Goym",
"common.saving": "Spar",
"common.saving": "Spar...",
"common.default": "Forsett",
"common.attachment": "viðheft",
"prompt.placeholder.shell": "Skriva eina shell-skipan {{example}}",
"prompt.placeholder.normal": 'Spyr nakað sum helst "{{example}}"',
"prompt.placeholder.simple": "Spyr um alt",
"prompt.placeholder.summarizeComments": "Samanber viðmerkingar",
"prompt.placeholder.summarizeComment": "Samanfata viðmerking",
"prompt.placeholder.shell": "Skriva eina shell-skipan... {{example}}",
"prompt.placeholder.normal": 'Spyr nakað sum helst... "{{example}}"',
"prompt.placeholder.simple": "Spyr um alt...",
"prompt.placeholder.summarizeComments": "Samanber viðmerkingar...",
"prompt.placeholder.summarizeComment": "Samanfata viðmerking...",
"prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc at fara út",
@@ -340,7 +340,7 @@ export const dict = {
"prompt.example.10": "Ger API skjøl",
"prompt.example.11": "Optimera dátugrunnsfyrispurningar",
"prompt.example.12": "Legg inntøkugóðkenning til",
"prompt.example.13": "Stovna ein nýggjan part til",
"prompt.example.13": "Stovna ein nýggjan part til...",
"prompt.example.14": "Hvussu seti eg hesa verkætlanina í verk?",
"prompt.example.15": "Gjøgnumganga mína kodu fyri bestu siðvenjur",
"prompt.example.16": "Legg feilhandfaring til hesa funku",
@@ -351,7 +351,7 @@ export const dict = {
"prompt.example.21": "Hjálp mær at skriva eitt flytingarrit",
"prompt.example.22": "Set í verk goymslu fyri hetta endapunktið",
"prompt.example.23": "Legg síðu til hendan listan",
"prompt.example.24": "Stovna eina CLI skipan til",
"prompt.example.24": "Stovna eina CLI skipan til...",
"prompt.example.25": "Hvussu virka umhvørvisbreytir her?",
"prompt.popover.emptyResults": "Eingi samsvarandi úrslit",
"prompt.popover.emptyCommands": "Ongar samsvarandi skipanir",
@@ -403,7 +403,7 @@ export const dict = {
"dialog.directory.parent": "Foreldur",
"dialog.directory.readError": "Tað ber ikki til at lesa hesa mappuna",
"app.server.unreachable": "Kundi ikki náa {{server}}.",
"app.server.retrying": "Roynir aftur sjálvvirkandi",
"app.server.retrying": "Roynir aftur sjálvvirkandi...",
"app.server.otherServers": "Aðrir ambætarar",
"dialog.server.title": "Ambætarar",
"dialog.server.description": "Skift hvønn OpenCode ambætara henda appin hevur samband við.",
@@ -413,7 +413,7 @@ export const dict = {
"dialog.server.add.url": "Ambætaraadressa",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Kundi ikki seta samband við ambætaran",
"dialog.server.add.checking": "Kanna",
"dialog.server.add.checking": "Kanna...",
"dialog.server.add.button": "Legg ambætara til",
"dialog.server.add.name": "Ambætaranavn (valfrítt)",
"dialog.server.add.namePlaceholder": "Localhost",
@@ -438,26 +438,26 @@ export const dict = {
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL ambætari",
"wsl.server.retryStart": "Royn aftur at byrja",
"wsl.server.updating": "Dagføring",
"wsl.server.updating": "Dagføring...",
"wsl.onboarding.step.distro": "Vel distro",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Kanna WSL",
"wsl.onboarding.checkingRuntime": "Kanna WSL...",
"wsl.onboarding.restartRequired": "Windows hevur brúk fyri endurbyrjan fyri at klára at seta upp WSL.",
"wsl.onboarding.ready": "WSL er klárt.",
"wsl.onboarding.required": "WSL er kravt fyri at halda fram.",
"wsl.onboarding.checkingDistros": "Kanna útbreiðslur",
"wsl.onboarding.installingDistro": "Seta upp {{distro}}",
"wsl.onboarding.checkingDistro": "Kanna {{distro}}",
"wsl.onboarding.listingDistros": "Skráseta útbreiðslur",
"wsl.onboarding.checkingDistros": "Kanna útbreiðslur...",
"wsl.onboarding.installingDistro": "Seta upp {{distro}}...",
"wsl.onboarding.checkingDistro": "Kanna {{distro}}...",
"wsl.onboarding.listingDistros": "Skráseta útbreiðslur...",
"wsl.onboarding.distroReady": "{{distro}} er klárt.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikki installerað enn.",
"wsl.onboarding.openDistroOnce": "Opna {{distro}} einaferð fyri at klára uppsetingina.",
"wsl.onboarding.finishingDistro": "Klára uppseting til {{distro}}.",
"wsl.onboarding.pickDistro": "Vel eina distro ella set eina upp niðanfyri.",
"wsl.onboarding.checkingOpencode": "Kanna OpenCode",
"wsl.onboarding.checkingOpencodeIn": "Kanna OpenCode í {{distro}}",
"wsl.onboarding.updatingOpencode": "Dagføring OpenCode",
"wsl.onboarding.updatingOpencodeIn": "Dagføring OpenCode í {{distro}}",
"wsl.onboarding.checkingOpencode": "Kanna OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Kanna OpenCode í {{distro}}...",
"wsl.onboarding.updatingOpencode": "Dagføring OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Dagføring OpenCode í {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Dagfør OpenCode í {{distro}}.",
"wsl.onboarding.updateOpencode": "Dagfør OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klárt í {{distro}}.",
@@ -466,11 +466,11 @@ export const dict = {
"wsl.onboarding.installOpencode": "Set upp OpenCode",
"wsl.onboarding.chooseDistroFirst": "Vel eina distro fyrst.",
"wsl.onboarding.loadFailed": "Tað eydnaðist ikki at heinta WSL tilstandin.",
"wsl.onboarding.loading": "Heinta",
"wsl.onboarding.loading": "Heinta...",
"wsl.onboarding.installedDistros": "Innsettar distros",
"wsl.onboarding.checkAgain": "Kanna aftur",
"wsl.onboarding.distroStatus.ready": "Klárt",
"wsl.onboarding.distroStatus.checking": "Kanna",
"wsl.onboarding.distroStatus.checking": "Kanna...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode ikki innstillað",
"wsl.onboarding.distroStatus.missingTools": "Manglar bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Óstuðlað · Brúka WSL 2",
@@ -489,7 +489,7 @@ export const dict = {
"wsl.onboarding.allDistrosAdded": "Allar uppsettar distros eru longu lagdar afturat.",
"wsl.onboarding.noDistros": "Ongar útbreiðslur eru funnar enn.",
"wsl.onboarding.install": "Set upp",
"wsl.onboarding.installing": "Seta upp",
"wsl.onboarding.installing": "Seta upp...",
"wsl.onboarding.installDistro": "Set distro upp",
"wsl.onboarding.searchDistros": "Leita eftir útbreiðslum",
"wsl.onboarding.wsl2Required": "WSL 2 er kravt.",
@@ -501,7 +501,7 @@ export const dict = {
"wsl.onboarding.unknown": "ókent",
"wsl.onboarding.desktopVersion": "skriviborð {{version}}",
"wsl.onboarding.versionMismatch": "Innsetta útgávan passar ikki til skriviborðsappiútgávuna.",
"wsl.onboarding.adding": "Leggi til",
"wsl.onboarding.adding": "Leggi til...",
"dialog.project.edit.title": "Rætta verkætlan",
"dialog.project.edit.name": "Navn",
"dialog.project.edit.icon": "Ikon",
@@ -607,7 +607,7 @@ export const dict = {
"error.page.action.report": "Melda feil",
"error.page.action.reported": "Feilur fráboðaður",
"error.page.action.exportLogs": "Útflyt loggar",
"error.page.action.checking": "Kanna",
"error.page.action.checking": "Kanna...",
"error.page.action.checkUpdates": "Kanna fyri dagføringum",
"error.page.action.updateTo": "Dagfør til {{version}}",
"error.page.circular": "[Rundskriv]",
@@ -686,12 +686,12 @@ export const dict = {
"session.review.filesChanged": "Fílur broyttar {{count}}",
"session.review.change.one": "Broyting",
"session.review.change.other": "Broytingar",
"session.review.loadingChanges": "Heinta broytingar",
"session.review.loadingChanges": "Heinta broytingar...",
"session.review.empty": "Ongar broytingar í hesi setuni enn",
"session.review.noVcs": "Nei Git Útgávustýringarskipan er uppdagað, broytingar ikki vístar",
"session.review.noVcs.createGit.title": "Stovna eitt Git goymslustað",
"session.review.noVcs.createGit.description": "Fylg við, kanna og angra broytingar í hesi verkætlan",
"session.review.noVcs.createGit.actionLoading": "Stovna Git goymslu",
"session.review.noVcs.createGit.actionLoading": "Stovna Git goymslu...",
"session.review.noVcs.createGit.action": "Stovna Git goymslu",
"session.review.noSnapshot": "Snapshot sporing er sløkt í config, so setubroytingar eru ikki tøkar",
"session.review.noChanges": "Ongar broytingar",
@@ -702,9 +702,9 @@ export const dict = {
"session.files.empty": "Ongar fílur",
"session.files.binaryContent": "Binær fíla (innihaldið kann ikki vísast)",
"session.messages.renderEarlier": "Vís fyrri boð",
"session.messages.loadingEarlier": "Heinta fyrri boð",
"session.messages.loadingEarlier": "Heinta fyrri boð...",
"session.messages.loadEarlier": "Innlesa fyrri boð",
"session.messages.loading": "Heinta boð",
"session.messages.loading": "Heinta boð...",
"session.messages.jumpToLatest": "Hopp til nýggjasta",
"session.context.addToContext": "Legg {{selection}} til samanhang",
"session.todo.title": "Uppgávur",
@@ -726,7 +726,7 @@ export const dict = {
"session.new.workspace.runIn": "Koyr setu í",
"session.new.workspace.triggerLocal": "Lokal",
"session.new.workspace.local": "Lokal goymsla",
"session.new.workspace.existing": "Workspace",
"session.new.workspace.existing": "Workspace...",
"session.new.git.none": "Nei Git",
"session.new.lastModified": "Seinast broytt",
"session.header.search.placeholder": "Leita {{project}}",
@@ -765,8 +765,8 @@ export const dict = {
"common.copied": "Avritað",
"lsp.tooltip.none": "Ongar LSP ambætarar",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Heinta boð",
"terminal.loading": "Innlesingarterminal",
"prompt.loading": "Heinta boð...",
"terminal.loading": "Innlesingarterminal...",
"terminal.title": "Farstøð",
"terminal.title.numbered": "Terminur {{number}}",
"terminal.close": "Lat terminalin aftur",
@@ -926,9 +926,9 @@ export const dict = {
"settings.updates.row.check.title": "Kanna fyri dagføringum",
"settings.updates.row.check.description": "Kanna manuelt fyri dagføringum og set upp, um tær eru tøkar",
"settings.updates.action.checkNow": "Kanna nú",
"settings.updates.action.checking": "Kanna",
"settings.updates.action.downloading": "Heinta",
"settings.updates.action.installing": "Seta upp",
"settings.updates.action.checking": "Kanna...",
"settings.updates.action.downloading": "Heinta...",
"settings.updates.action.installing": "Seta upp...",
"settings.updates.toast.latest.title": "Tú ert dagførdur",
"settings.updates.toast.latest.description": "Tú koyrir nýggjastu útgávuna av OpenCode.",
"sound.option.none": "Eingin",
@@ -1075,7 +1075,7 @@ export const dict = {
"workspace.reset.success.title": "Workspace nullstillað",
"workspace.reset.success.description": "Workspace passar nú til forsettu greinina.",
"workspace.error.stillPreparing": "Workspace verður enn fyrireikað",
"workspace.status.checking": "Kanna fyri ósamanlagdum broytingum",
"workspace.status.checking": "Kanna fyri ósamanlagdum broytingum...",
"workspace.status.error": "Ikki ber til at staðfesta git-støðuna.",
"workspace.status.clean": "Ongar ósamanlagdar broytingar eru funnar.",
"workspace.status.dirty": "Ósamanlagdar broytingar funnar í hesum workspace.",

Some files were not shown because too many files have changed in this diff Show More