diff --git a/bun.lock b/bun.lock index 624b655bb03..560952fd1a9 100644 --- a/bun.lock +++ b/bun.lock @@ -657,7 +657,6 @@ "open": "10.1.2", "opencode-gitlab-auth": "2.1.0", "opencode-poe-auth": "0.0.1", - "opentui-spinner": "catalog:", "partial-json": "0.1.7", "remeda": "catalog:", "ripgrep": "0.3.1", @@ -925,7 +924,6 @@ "effect": "catalog:", "fuzzysort": "catalog:", "open": "10.1.2", - "opentui-spinner": "catalog:", "remeda": "catalog:", "solid-js": "catalog:", "strip-ansi": "7.1.2", @@ -1085,7 +1083,6 @@ "luxon": "3.6.1", "marked": "17.0.6", "marked-shiki": "1.2.1", - "opentui-spinner": "0.0.7", "remeda": "2.26.0", "remend": "1.3.0", "semver": "7.7.4", @@ -3068,7 +3065,7 @@ "cli-sound": ["cli-sound@1.1.3", "", { "dependencies": { "find-exec": "^1.0.3" }, "bin": { "cli-sound": "dist/esm/cli.js" } }, "sha512-dpdF3KS3wjo1fobKG5iU9KyKqzQWAqueymHzZ9epus/dZ40487gAvS6aXFeBul+GiQAQYUTAtUWgQvw6Jftbyg=="], - "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], "cli-truncate": ["cli-truncate@3.1.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^5.0.0" } }, "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA=="], @@ -4136,8 +4133,6 @@ "openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="], - "opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="], - "option": ["option@0.2.4", "", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -4904,8 +4899,6 @@ "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], @@ -5772,12 +5765,6 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "opentui-spinner/@opentui/core": ["@opentui/core@0.3.4", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.3.4", "@opentui/core-darwin-x64": "0.3.4", "@opentui/core-linux-arm64": "0.3.4", "@opentui/core-linux-arm64-musl": "0.3.4", "@opentui/core-linux-x64": "0.3.4", "@opentui/core-linux-x64-musl": "0.3.4", "@opentui/core-win32-arm64": "0.3.4", "@opentui/core-win32-x64": "0.3.4" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-y0DlrChP9lcJ4jC5z/1wMS34+ygfSTW7gD5OJHwJaAScfmlFvuJOZbwmCGrJURZ+5wFBxuOi9LatZsmeAUIKAA=="], - - "opentui-spinner/@opentui/solid": ["@opentui/solid@0.3.4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.3.4", "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-gin1VnsVBahX0nrU3mpgh5U1qvyJBIZu4NE5mc0YnObWOEf9HVNxKY4/BpUvQPh91kT6zeOzTBvAvYK4R7g9MQ=="], - - "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - "ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], "p-filter/p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="], @@ -6482,28 +6469,6 @@ "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4A7JYXUsZqhu9PPCe07E30ourSJYkitkwMujUyNKjM5e/dHNDVnz+5r5cO3M5snofLafc1DN7+9jEPn4UQzchQ=="], - - "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Jvm9E8n2sPhKEyKSXn9GlmJcj8WoJXJTooXb3djwjVaiimjihIj0XxHzCWhdqbDtQp+VxDFyCKoQagOOz20qhA=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-0uPuHCeZxm/O7+L+iNQl8zRAfehiwYstKkT9J0uTZO64/byBCLvy5lvn1DiE/72s/nTJ5nwpLN+pQs2/WYVKLQ=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-sJYUzYcSOb5PCXRlhwsse/fdsMiVomNvIwq/2TDhAANef+YPO3Br+OH9kQRbuj0bjVDmUS36SGYWSTFu2lUO+A=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-btYIQeNdPbN4JCrCjVB/RwMGrnRY7qWB2piNEfALSByuULKNjPKQ33PYIj38Yd01zCvCV7FotIeXEGSHx3tgCA=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-fhmUey4oJJ2+N62xlIgAPxAl36Fa7wYffqDOT4QLpm0jfyD5xzo+wL/hr2zUqaEI439R8Iq6jHNxf/Nsx1WuuQ=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-sh432vPU+eLp8eA4I0KWKKn7D0VHbk01YTg6mA9/ihCNYHntc6LZ8/sLvsPv8CvKscMotfIkh3M5YhdS36BuXw=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-dw8FcjUZaLAjw25P3/7BarobCh/QOHn3srYaWYQdysoqyvSlPkQumpI8kV/KgpJtdITU1GW02MQC4EeLIFFalA=="], - - "opentui-spinner/@opentui/core/bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="], - - "opentui-spinner/@opentui/core/marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], - - "opentui-spinner/@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], - "ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -6758,8 +6723,6 @@ "mocha/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "opentui-spinner/@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "pkg-conf/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], diff --git a/package.json b/package.json index 536136f32e5..2432d4a2f13 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@cloudflare/workers-types": "4.20251008.0", "@openauthjs/openauth": "0.0.0-20250322224806", "@pierre/diffs": "1.2.10", - "opentui-spinner": "0.0.7", "@solid-primitives/storage": "4.3.3", "@tailwindcss/vite": "4.1.11", "diff": "8.0.4", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 9e567324b91..57b6f501845 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -154,7 +154,6 @@ "open": "10.1.2", "opencode-gitlab-auth": "2.1.0", "opencode-poe-auth": "0.0.1", - "opentui-spinner": "catalog:", "remeda": "catalog:", "ripgrep": "0.3.1", "semver": "^7.6.3", diff --git a/packages/tui/package.json b/packages/tui/package.json index d6772cde4b4..6a8c46a73d7 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -61,7 +61,6 @@ "effect": "catalog:", "fuzzysort": "catalog:", "open": "10.1.2", - "opentui-spinner": "catalog:", "remeda": "catalog:", "strip-ansi": "7.1.2", "solid-js": "catalog:" diff --git a/packages/tui/src/component/register-spinner.ts b/packages/tui/src/component/register-spinner.ts index 9626cea4cea..7dc9b672aa2 100644 --- a/packages/tui/src/component/register-spinner.ts +++ b/packages/tui/src/component/register-spinner.ts @@ -1,6 +1,139 @@ -import { getComponentCatalogue } from "@opentui/solid/components" -import { registerSpinner } from "opentui-spinner/solid" +// kilocode_change start - register against Kilo's active OpenTUI runtime instead of opentui-spinner's nested 0.3 runtime +import { + type ColorInput, + type OptimizedBuffer, + parseColor, + Renderable, + type RenderableOptions, + type RenderContext, + resolveRenderLib, +} from "@opentui/core" +import { extend, getComponentCatalogue } from "@opentui/solid/components" +import type { ColorGenerator } from "../ui/spinner" + +interface SpinnerOptions extends RenderableOptions { + frames?: string[] + interval?: number + color?: ColorInput | ColorGenerator +} + +class SpinnerRenderable extends Renderable { + private list: string[] + private delay: number + private tone: ColorInput | ColorGenerator + private parsed = parseColor("white") + private frame = 0 + private encoded: Record>> = {} + private lib = resolveRenderLib() + private timer: ReturnType | undefined + + constructor(ctx: RenderContext, options: SpinnerOptions) { + super(ctx, options) + this.list = options.frames?.length ? options.frames : ["⠋"] + this.delay = options.interval ?? 80 + this.tone = options.color ?? "white" + if (typeof this.tone !== "function") this.parsed = parseColor(this.tone) + this.height = 1 + this.encode() + this.start() + } + + private encode() { + this.encoded = {} + this.width = 0 + for (const frame of this.list) { + const encoded = this.lib.encodeUnicode(frame, this.ctx.widthMethod) + if (!encoded) continue + this.encoded[frame] = encoded + this.width = Math.max( + this.width, + encoded.data.reduce((width, char) => width + char.width, 0), + ) + } + } + + private free() { + for (const frame of Object.values(this.encoded)) this.lib.freeUnicode(frame) + this.encoded = {} + } + + private start() { + if (this.timer) clearInterval(this.timer) + this.timer = setInterval(() => { + if (this.isDestroyed || !this.visible) return + this.frame = (this.frame + 1) % this.list.length + this.requestRender() + }, this.delay) + this.timer.unref() + } + + get frames() { + return this.list + } + + set frames(value: string[]) { + if ( + value.length === 0 || + (value.length === this.list.length && value.every((frame, index) => frame === this.list[index])) + ) + return + this.free() + this.list = value + this.frame = 0 + this.encode() + this.requestRender() + } + + get interval() { + return this.delay + } + + set interval(value: number) { + if (value === this.delay) return + this.delay = value + this.start() + } + + get color() { + return this.tone + } + + set color(value: ColorInput | ColorGenerator) { + this.tone = value + if (typeof value !== "function") this.parsed = parseColor(value) + this.requestRender() + } + + protected override renderSelf(buffer: OptimizedBuffer) { + const frame = this.encoded[this.list[this.frame]] + if (!frame) return + const background = parseColor("transparent") + let x = this.x + for (let index = 0; index < frame.data.length; index++) { + const char = frame.data[index] + const color = + typeof this.tone === "function" + ? parseColor(this.tone(this.frame, index, this.list.length, frame.data.length)) + : this.parsed + buffer.drawChar(char.char, x, this.y, color, background) + x += char.width + } + } + + protected override destroySelf() { + if (this.timer) clearInterval(this.timer) + this.free() + super.destroySelf() + } +} + +declare module "@opentui/solid" { + interface OpenTUIComponents { + spinner: typeof SpinnerRenderable + } +} export function registerOpencodeSpinner() { - if (!getComponentCatalogue().spinner) registerSpinner() + if (!getComponentCatalogue().spinner) extend({ spinner: SpinnerRenderable }) } +// kilocode_change end diff --git a/packages/tui/src/ui/spinner.ts b/packages/tui/src/ui/spinner.ts index c185ea7b833..424113a04b0 100644 --- a/packages/tui/src/ui/spinner.ts +++ b/packages/tui/src/ui/spinner.ts @@ -1,6 +1,9 @@ import type { ColorInput } from "@opentui/core" import { RGBA } from "@opentui/core" -import type { ColorGenerator } from "opentui-spinner" + +// kilocode_change start - local spinner renderable stays on Kilo's active OpenTUI runtime +export type ColorGenerator = (frame: number, char: number, frames: number, chars: number) => ColorInput +// kilocode_change end interface AdvancedGradientOptions { colors: ColorInput[] diff --git a/packages/tui/test/kilocode/spinner-runtime.test.ts b/packages/tui/test/kilocode/spinner-runtime.test.ts new file mode 100644 index 00000000000..5006527ab38 --- /dev/null +++ b/packages/tui/test/kilocode/spinner-runtime.test.ts @@ -0,0 +1,11 @@ +import { expect, test } from "bun:test" +import { Renderable } from "@opentui/core" +import { getComponentCatalogue } from "@opentui/solid/components" +import { registerOpencodeSpinner } from "../../src/component/register-spinner" + +test("spinner uses the active OpenTUI runtime", () => { + registerOpencodeSpinner() + const spinner = getComponentCatalogue().spinner + expect(spinner).toBeDefined() + expect(spinner?.prototype).toBeInstanceOf(Renderable) +}) diff --git a/packages/ui/src/i18n/it.ts b/packages/ui/src/i18n/it.ts index fc0947b9723..0550975b5c1 100644 --- a/packages/ui/src/i18n/it.ts +++ b/packages/ui/src/i18n/it.ts @@ -16,22 +16,22 @@ export const dict: Record = { "ui.sessionReview.largeDiff.title": "Diff troppo grande da mostrare", "ui.sessionReview.largeDiff.meta": "Limite: {{limit}} righe modificate. Corrente: {{current}} righe modificate.", "ui.sessionReview.largeDiff.renderAnyway": "Mostra comunque", - "ui.sessionReviewV2.expandMode": "Expand or collapse diff", - "ui.sessionReviewV2.filterFiles": "Filter files", - "ui.sessionReviewV2.toggleSidebar": "Toggle file tree", - "ui.sessionReviewV2.showAllLines": "Show all lines", - "ui.sessionReviewV2.hideNonDiffLines": "Hide non-diff lines", - "ui.sessionReviewV2.unifiedDiff": "Unified diff", - "ui.sessionReviewV2.splitDiff": "Split diff", - "ui.sessionReviewV2.previousFile": "Previous file", - "ui.sessionReviewV2.nextFile": "Next file", - "ui.sessionReviewV2.diffView": "Diff view", - "ui.sessionReviewV2.empty.noGit.title": "No tracked changes", - "ui.sessionReviewV2.empty.noGit.description": "Track, review, and undo changes in this project", - "ui.sessionReviewV2.empty.noGit.action": "Create Git repository", - "ui.sessionReviewV2.empty.noGit.actionLoading": "Creating Git repository...", - "ui.sessionReviewV2.empty.changes.title": "No file changes yet", - "ui.sessionReviewV2.empty.changes.description": "Project changes will appear here", + "ui.sessionReviewV2.expandMode": "Espandi o comprimi il diff", + "ui.sessionReviewV2.filterFiles": "Filtra file", + "ui.sessionReviewV2.toggleSidebar": "Mostra o nascondi l'albero dei file", + "ui.sessionReviewV2.showAllLines": "Mostra tutte le righe", + "ui.sessionReviewV2.hideNonDiffLines": "Nascondi le righe non modificate", + "ui.sessionReviewV2.unifiedDiff": "Diff unificato", + "ui.sessionReviewV2.splitDiff": "Diff diviso", + "ui.sessionReviewV2.previousFile": "File precedente", + "ui.sessionReviewV2.nextFile": "File successivo", + "ui.sessionReviewV2.diffView": "Vista diff", + "ui.sessionReviewV2.empty.noGit.title": "Nessuna modifica tracciata", + "ui.sessionReviewV2.empty.noGit.description": "Traccia, rivedi e annulla le modifiche in questo progetto", + "ui.sessionReviewV2.empty.noGit.action": "Crea repository Git", + "ui.sessionReviewV2.empty.noGit.actionLoading": "Creazione repository Git...", + "ui.sessionReviewV2.empty.changes.title": "Ancora nessuna modifica ai file", + "ui.sessionReviewV2.empty.changes.description": "Le modifiche al progetto appariranno qui", "ui.sessionReview.openFile": "Apri file", "ui.sessionReview.selection.line": "riga {{line}}", "ui.sessionReview.selection.lines": "righe {{start}}-{{end}}", @@ -52,15 +52,15 @@ export const dict: Record = { "ui.lineComment.editorLabel.suffix": "", "ui.lineComment.placeholder": "Aggiungi commento", "ui.lineComment.submit": "Commenta", - "ui.lineComment.cancel": "Cancel", + "ui.lineComment.cancel": "Annulla", "ui.sessionTurn.steps.show": "Mostra passaggi", "ui.sessionTurn.steps.hide": "Nascondi passaggi", "ui.sessionTurn.summary.response": "Risposta", "ui.sessionTurn.diff.showMore": "Mostra altre modifiche ({{count}})", "ui.sessionTurn.diffs.changed": "Modificato", - "ui.sessionTurn.diffs.changed.one": "{{count}} Changed file", - "ui.sessionTurn.diffs.changed.other": "{{count}} Changed files", + "ui.sessionTurn.diffs.changed.one": "{{count}} file modificato", + "ui.sessionTurn.diffs.changed.other": "{{count}} file modificati", "ui.sessionTurn.diffs.showAll": "Mostra tutto", "ui.sessionTurn.diffs.showLess": "Mostra meno", "ui.sessionTurn.diffs.more": "+{{count}} file aggiuntivi", @@ -180,7 +180,7 @@ export const dict: Record = { "ui.common.close": "Chiudi", "ui.common.next": "Avanti", "ui.common.submit": "Invia", - "ui.common.showMore": "Show more", + "ui.common.showMore": "Mostra altro", "ui.permission.deny": "Nega", "ui.permission.allowAlways": "Consenti sempre", diff --git a/packages/ui/src/i18n/nl.ts b/packages/ui/src/i18n/nl.ts index df421c92fb3..a4c566c752b 100644 --- a/packages/ui/src/i18n/nl.ts +++ b/packages/ui/src/i18n/nl.ts @@ -16,22 +16,22 @@ export const dict: Record = { "ui.sessionReview.largeDiff.title": "Diff te groot om weer te geven", "ui.sessionReview.largeDiff.meta": "Limiet: {{limit}} gewijzigde regels. Huidig: {{current}} gewijzigde regels.", "ui.sessionReview.largeDiff.renderAnyway": "Toch weergeven", - "ui.sessionReviewV2.expandMode": "Expand or collapse diff", - "ui.sessionReviewV2.filterFiles": "Filter files", - "ui.sessionReviewV2.toggleSidebar": "Toggle file tree", - "ui.sessionReviewV2.showAllLines": "Show all lines", - "ui.sessionReviewV2.hideNonDiffLines": "Hide non-diff lines", - "ui.sessionReviewV2.unifiedDiff": "Unified diff", - "ui.sessionReviewV2.splitDiff": "Split diff", - "ui.sessionReviewV2.previousFile": "Previous file", - "ui.sessionReviewV2.nextFile": "Next file", - "ui.sessionReviewV2.diffView": "Diff view", - "ui.sessionReviewV2.empty.noGit.title": "No tracked changes", - "ui.sessionReviewV2.empty.noGit.description": "Track, review, and undo changes in this project", - "ui.sessionReviewV2.empty.noGit.action": "Create Git repository", - "ui.sessionReviewV2.empty.noGit.actionLoading": "Creating Git repository...", - "ui.sessionReviewV2.empty.changes.title": "No file changes yet", - "ui.sessionReviewV2.empty.changes.description": "Project changes will appear here", + "ui.sessionReviewV2.expandMode": "Diff uit- of inklappen", + "ui.sessionReviewV2.filterFiles": "Bestanden filteren", + "ui.sessionReviewV2.toggleSidebar": "Bestandsboom tonen of verbergen", + "ui.sessionReviewV2.showAllLines": "Alle regels tonen", + "ui.sessionReviewV2.hideNonDiffLines": "Ongewijzigde regels verbergen", + "ui.sessionReviewV2.unifiedDiff": "Gecombineerde diff", + "ui.sessionReviewV2.splitDiff": "Gesplitste diff", + "ui.sessionReviewV2.previousFile": "Vorig bestand", + "ui.sessionReviewV2.nextFile": "Volgend bestand", + "ui.sessionReviewV2.diffView": "Diffweergave", + "ui.sessionReviewV2.empty.noGit.title": "Geen bijgehouden wijzigingen", + "ui.sessionReviewV2.empty.noGit.description": "Wijzigingen in dit project bijhouden, beoordelen en ongedaan maken", + "ui.sessionReviewV2.empty.noGit.action": "Git-repository maken", + "ui.sessionReviewV2.empty.noGit.actionLoading": "Git-repository maken...", + "ui.sessionReviewV2.empty.changes.title": "Nog geen bestandswijzigingen", + "ui.sessionReviewV2.empty.changes.description": "Projectwijzigingen verschijnen hier", "ui.sessionReview.openFile": "Bestand openen", "ui.sessionReview.selection.line": "regel {{line}}", "ui.sessionReview.selection.lines": "regels {{start}}-{{end}}", @@ -52,15 +52,15 @@ export const dict: Record = { "ui.lineComment.editorLabel.suffix": "", "ui.lineComment.placeholder": "Opmerking toevoegen", "ui.lineComment.submit": "Reageren", - "ui.lineComment.cancel": "Cancel", + "ui.lineComment.cancel": "Annuleren", "ui.sessionTurn.steps.show": "Stappen tonen", "ui.sessionTurn.steps.hide": "Stappen verbergen", "ui.sessionTurn.summary.response": "Antwoord", "ui.sessionTurn.diff.showMore": "Toon meer wijzigingen ({{count}})", "ui.sessionTurn.diffs.changed": "Gewijzigd", - "ui.sessionTurn.diffs.changed.one": "{{count}} Changed file", - "ui.sessionTurn.diffs.changed.other": "{{count}} Changed files", + "ui.sessionTurn.diffs.changed.one": "{{count}} gewijzigd bestand", + "ui.sessionTurn.diffs.changed.other": "{{count}} gewijzigde bestanden", "ui.sessionTurn.diffs.showAll": "Alles tonen", "ui.sessionTurn.diffs.showLess": "Minder tonen", "ui.sessionTurn.diffs.more": "+{{count}} extra bestanden", @@ -184,7 +184,7 @@ export const dict: Record = { "ui.common.close": "Sluiten", "ui.common.next": "Volgende", "ui.common.submit": "Verzenden", - "ui.common.showMore": "Show more", + "ui.common.showMore": "Meer tonen", "ui.permission.deny": "Weigeren", "ui.permission.allowAlways": "Altijd toestaan", diff --git a/script/upstream/transforms/transform-i18n.test.ts b/script/upstream/transforms/transform-i18n.test.ts index 399f51c9358..a604d8ec55b 100644 --- a/script/upstream/transforms/transform-i18n.test.ts +++ b/script/upstream/transforms/transform-i18n.test.ts @@ -1,12 +1,26 @@ import { expect, test } from "bun:test" import { transformI18nContent } from "./transform-i18n" +import { translate } from "../utils/upstream" test("marks transformed Kilo branding and preserves legacy config names", () => { const result = transformI18nContent( ' "product": "OpenCode",\n "docs": "https://opencode.ai/docs",\n "legacy": ".opencode/opencode.json",', + false, + true, ) expect(result.result).toContain('"product": "Kilo", // kilocode_change') expect(result.result).toContain('"docs": "https://kilo.ai/docs", // kilocode_change') expect(result.result).toContain('"legacy": ".opencode/opencode.json",') expect(result.replacements).toBe(2) }) + +test("does not inject source markers into non-locale content", () => { + const result = transformI18nContent("OpenCode uses opencode serve") + expect(result.result).toBe("Kilo uses kilo serve") +}) + +test("generic upstream translation keeps prompt text marker-free", async () => { + const result = await translate("packages/opencode/src/session/prompt/meta.txt", "OpenCode uses opencode serve") + expect(result).toBe("Kilo uses kilo serve") + expect(result).not.toContain("kilocode_change") +}) diff --git a/script/upstream/transforms/transform-i18n.ts b/script/upstream/transforms/transform-i18n.ts index 4e2e63a1839..ffc4b29ac78 100644 --- a/script/upstream/transforms/transform-i18n.ts +++ b/script/upstream/transforms/transform-i18n.ts @@ -148,6 +148,7 @@ function shouldPreserveLine(line: string): boolean { export function transformI18nContent( content: string, verbose = false, + markers = false, ): { result: string; replacements: number; preserved: number } { const lines = content.split("\n") const transformedLines: string[] = [] @@ -201,7 +202,7 @@ export function transformI18nContent( } // Kilo branding produced by this transform remains a Kilo-owned delta in shared locale files. - transformedLines.push(lineReplacements > 0 ? `${transformedLine} // kilocode_change` : transformedLine) + transformedLines.push(markers && lineReplacements > 0 ? `${transformedLine} // kilocode_change` : transformedLine) totalReplacements += lineReplacements } @@ -222,7 +223,7 @@ export async function transformI18nFile( const file = Bun.file(filePath) const content = await file.text() - const { result, replacements, preserved } = transformI18nContent(content, options.verbose) + const { result, replacements, preserved } = transformI18nContent(content, options.verbose, true) if (replacements > 0 && !options.dryRun) { await Bun.write(filePath, result) diff --git a/script/upstream/transforms/transform-package-json.test.ts b/script/upstream/transforms/transform-package-json.test.ts index 49e6524eaf0..bd8bb288b44 100644 --- a/script/upstream/transforms/transform-package-json.test.ts +++ b/script/upstream/transforms/transform-package-json.test.ts @@ -10,6 +10,7 @@ import { mergeWithNewestVersions, prunePatchedDependencies, selectBunPackageManager, + transformDependencies, } from "./transform-package-json" test("fixScripts preserves Kilo-only root scripts from base", () => { @@ -99,7 +100,12 @@ test("fixScripts preserves dev:local and shared-package test:ci scripts", () => const junit = "mkdir -p .artifacts/unit && bun test --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml" const root: Record = { scripts: { dev: "bun dev" } } const changes: string[] = [] - fixScripts(root, "package.json", { scripts: { "dev:local": "bun run packages/opencode/script/dev-local.ts" } }, changes) + fixScripts( + root, + "package.json", + { scripts: { "dev:local": "bun run packages/opencode/script/dev-local.ts" } }, + changes, + ) expect((root.scripts as Record)["dev:local"]).toBe("bun run packages/opencode/script/dev-local.ts") for (const path of [ @@ -158,12 +164,13 @@ test("fixScripts leaves unknown packages untouched", () => { expect(changes.length).toBe(0) }) -test("fixCatalog removes upstream-only desktop sentry entries", () => { +test("fixCatalog removes unsupported upstream entries", () => { const pkg: Record = { workspaces: { catalog: { "@sentry/solid": "10.36.0", "@sentry/vite-plugin": "4.6.0", + "opentui-spinner": "0.0.7", "solid-js": "1.9.12", }, }, @@ -173,8 +180,9 @@ test("fixCatalog removes upstream-only desktop sentry entries", () => { const cat = (pkg.workspaces as { catalog: Record }).catalog expect(cat["@sentry/solid"]).toBeUndefined() expect(cat["@sentry/vite-plugin"]).toBeUndefined() + expect(cat["opentui-spinner"]).toBeUndefined() expect(cat["solid-js"]).toBe("1.9.12") - expect(changes.length).toBe(2) + expect(changes.length).toBe(3) }) test("fixCatalog is a no-op when catalog is absent", () => { @@ -184,6 +192,12 @@ test("fixCatalog is a no-op when catalog is absent", () => { expect(changes.length).toBe(0) }) +test("transformDependencies removes the incompatible spinner runtime", () => { + const result = transformDependencies({ "opentui-spinner": "catalog:", "solid-js": "catalog:" }) + expect(result.result).toEqual({ "solid-js": "catalog:" }) + expect(result.changes).toEqual(["opentui-spinner: removed (incompatible OpenTUI runtime)"]) +}) + test("fixMetadata preserves opencode publish metadata from base", () => { const ours = { keywords: ["cli", "kilo", "opencode"], private: false } const pkg: Record = { keywords: ["opencode"], private: true } diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts index 1532ac841fd..ed3981a7882 100644 --- a/script/upstream/transforms/transform-package-json.ts +++ b/script/upstream/transforms/transform-package-json.ts @@ -328,9 +328,11 @@ const DELETE_UPSTREAM_SCRIPTS: Record = { // in by upstream features (e.g. desktop Sentry integration) that Kilo doesn't // ship, so they add install weight with zero consumers in our tree. const DELETE_UPSTREAM_CATALOG: Record = { - "package.json": ["@sentry/solid", "@sentry/vite-plugin"], + "package.json": ["@sentry/solid", "@sentry/vite-plugin", "opentui-spinner"], } +const DELETE_UPSTREAM_DEPENDENCIES = new Set(["opentui-spinner"]) + /** * Re-apply Kilo-specific scripts on top of the upstream-shaped scripts block, * and prune upstream-only scripts that target packages Kilo doesn't ship. @@ -449,7 +451,7 @@ export function isPackageJson(file: string): boolean { /** * Transform dependencies in package.json */ -function transformDependencies(deps: Record | undefined): { +export function transformDependencies(deps: Record | undefined): { result: Record changes: string[] } { @@ -459,6 +461,10 @@ function transformDependencies(deps: Record | undefined): { const changes: string[] = [] for (const [name, version] of Object.entries(deps)) { + if (DELETE_UPSTREAM_DEPENDENCIES.has(name)) { + changes.push(`${name}: removed (incompatible OpenTUI runtime)`) + continue + } const newName = PACKAGE_NAME_MAP[name] if (newName) { result[newName] = version diff --git a/script/upstream/utils/upstream.ts b/script/upstream/utils/upstream.ts index ab4107c40db..9a40069de4c 100644 --- a/script/upstream/utils/upstream.ts +++ b/script/upstream/utils/upstream.ts @@ -4,7 +4,7 @@ import { $ } from "bun" import path from "node:path" import { applyPackageNameTransforms } from "../transforms/package-names" import { applyExtensionTransforms } from "../transforms/transform-extensions" -import { transformI18nContent } from "../transforms/transform-i18n" +import { isI18nFile, transformI18nContent } from "../transforms/transform-i18n" import { applyScriptTransforms } from "../transforms/transform-scripts" import { applyBrandingTransforms } from "../transforms/transform-take-theirs" import { applyWebTransforms } from "../transforms/transform-web" @@ -191,7 +191,7 @@ export async function translate(file: string, text: string) { const names = applyPackageNameTransforms(text).result const script = applyScriptTransforms(names).result const branded = applyBrandingTransforms(script).result - const i18n = transformI18nContent(branded).result + const i18n = transformI18nContent(branded, false, isI18nFile(file)).result const ext = applyExtensionTransforms(i18n, file).result const web = applyWebTransforms(ext).result