From 9d4e4e5e719050bab3c1637e20e634d92f994f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Sat, 19 Sep 2026 22:35:33 +0000 Subject: [PATCH] fix(mobile): show remote CLI copy without literal markdown backticks Explorer finding: tour-runwhere: The 'Your computer' card renders literal markdown backticks around `kilo remote` in user-facing copy. The user-agent explorer found this while using the app like a user. One finding per item; the explorer never edits product code. Flow: tour-runwhere Found on revision: fd04ca1ef Repro: 1. set this state first: brand-new first-run account e2e-firstrun-1789794214@example.com with $10 credits, a second organization, 3 Code Reviewer pull requests, one top-level session `question` (ses_f47e65f26ffeo07Rjk0jgg7YM5); device portrait, system day mode, default font. 2. open the app on emulator-5602 3. reach the screen the capture names (tour-runwhere) 4. the capture shows the defect named below Observed: The 'Your computer' card renders literal markdown backticks around `kilo remote` in user-facing copy. Expected: the screen renders without this defect Evidence (from the device run): - ~/.local/share/kwf/findings/explorer-tour-runwhere-the-your-computer-card-renders-lit-c14a6b3a/tour-runwhere.png Production record (automatic, 2026-09-19): ## Production record for `ses_f47e65f26ffeo07Rjk0jgg7YM5` ### Axiom, last 72 h No line carries this term in: `vercel`, `cloudflare-logpush`, `supabase-production`. ### The session row, read replica No row in `cli_sessions_v2` carries `ses_f47e65f26ffeo07Rjk0jgg7YM5`. ### Log archives, R2 `kilocode-sessions` NOT READ: R2 keys are indexed by the agent id, not the session id; take the agent_* from the worker logs --- apps/mobile/src/i18n/locales/en.json | 8 +-- apps/mobile/src/i18n/plain-text-copy.test.ts | 70 ++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 apps/mobile/src/i18n/plain-text-copy.test.ts diff --git a/apps/mobile/src/i18n/locales/en.json b/apps/mobile/src/i18n/locales/en.json index 9940067642..12ebdbbaa2 100644 --- a/apps/mobile/src/i18n/locales/en.json +++ b/apps/mobile/src/i18n/locales/en.json @@ -2190,7 +2190,7 @@ "discardDraftMessage": "Your prompt will be lost.", "discardWithUploadsMessage": "Your prompt and any unclaimed uploads will be deleted.", "discardFailed": "Could not discard the draft. Please try again.", - "remoteHint": "Run `kilo remote` on your computer, or `/remote` in a running CLI session, to control a local kilo process.", + "remoteHint": "Run kilo remote on your computer, or /remote in a running CLI session, to control a local kilo process.", "continueFrom": "Continuing {{title}}", "importingSession": "Importing session", "cliCannotContinue": "This CLI cannot continue a session. Pick Cloud Agent or another CLI.", @@ -2247,7 +2247,7 @@ "cloudAgent": "Cloud Agent", "cloudAgentDescription": "Run on Kilo's cloud sandbox", "noCliInstances": "No CLI instances connected", - "noCliInstancesDescription": "Run `kilo remote` in a project on your computer, or update Kilo CLI if one is already running.", + "noCliInstancesDescription": "Run kilo remote in a project on your computer, or update Kilo CLI if one is already running.", "instanceOnProject": "{{name}} on {{project}}", "instanceOnProjectSuffix": "{{name}} on {{project}} ({{suffix}})", "runOnCloudAgent": "Run on Cloud Agent" @@ -3176,14 +3176,14 @@ "cloudOptionTitle": "$t(agentChat.instancePicker.cloudAgent)", "cloudOptionBody": "Run Kilo in a sandboxed cloud environment with nothing to install.", "remoteOptionTitle": "Your computer", - "remoteOptionBody": "Run Kilo on your own machine through the `kilo remote` CLI.", + "remoteOptionBody": "Run Kilo on your own machine through the kilo remote CLI.", "skip": "Skip tour", "tutorialLabel": "Tutorial", "remoteTitle": "Start your first remote session", "remoteBody": "Connect your computer, then choose it below to start a new session.", "remoteRunHint": "$t(agentChat.newSession.remoteHint)", "remoteEmptyTitle": "No computer connected yet", - "remoteEmptyBody": "Run `kilo remote` on your computer to connect it.", + "remoteEmptyBody": "Run kilo remote on your computer to connect it.", "remoteFound": "Computer connected", "checkAgain": "Check again", "networkError": "$t(authErrors.networkError)" diff --git a/apps/mobile/src/i18n/plain-text-copy.test.ts b/apps/mobile/src/i18n/plain-text-copy.test.ts new file mode 100644 index 0000000000..608875f522 --- /dev/null +++ b/apps/mobile/src/i18n/plain-text-copy.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest'; + +import en from './locales/en.json'; + +/** + * Catalog copy reaches the screen through plain `` (or the mono variant): + * the app has no inline-markdown renderer, so a markdown backtick in a value is + * shown to the reader literally. The tour's "Your computer" card rendered + * `` `kilo remote` `` with its backticks, so English — the source of truth for + * the other catalogs — must stay free of them. + */ +const RUN_COPY = [ + { + key: 'tour.remoteOptionBody', + value: 'Run Kilo on your own machine through the kilo remote CLI.', + }, + { + key: 'tour.remoteEmptyBody', + value: 'Run kilo remote on your computer to connect it.', + }, + { + key: 'agentChat.newSession.remoteHint', + value: + 'Run kilo remote on your computer, or /remote in a running CLI session, to control a local kilo process.', + }, + { + key: 'agentChat.instancePicker.noCliInstancesDescription', + value: + 'Run kilo remote in a project on your computer, or update Kilo CLI if one is already running.', + }, +] as const; + +function lookup(path: string): unknown { + let node: unknown = en; + for (const part of path.split('.')) { + if (!node || typeof node !== 'object' || !(part in node)) { + return undefined; + } + node = (node as Record)[part]; + } + return node; +} + +describe('plain-text copy', () => { + it.each(RUN_COPY)('keeps $key free of markdown backticks', ({ key, value }) => { + expect(lookup(key)).toBe(value); + expect(value).not.toContain('`'); + }); + + it('has no markdown backticks anywhere in the English catalog', () => { + const offenders: string[] = []; + const walk = (node: unknown, path: string): void => { + if (typeof node === 'string') { + if (node.includes('`')) { + offenders.push(`${path}: ${node}`); + } + return; + } + if (node && typeof node === 'object') { + for (const [key, child] of Object.entries(node)) { + walk(child, path ? `${path}.${key}` : key); + } + } + }; + + walk(en, ''); + + expect(offenders).toEqual([]); + }); +});