agentic-tdd
Test-driven development for behaviour a unit test cannot reach, by writing the expectation…
Prove a language switch translates what the user sees, keeps what they had already typed, and is still in force after a reload. Use when i18n, a locale picker, a language menu or translation files were added or changed, when a string is suspected of staying in the old language
$ npx -y skills add reticlehq/reticle --skill verify-language-switch --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/verify-language-switchContext preview
The summary Claude sees to decide when to auto-load this skill.
Prove a language switch translates what the user sees, keeps what they had already typed, and is still in force after a reload. Use when i18n, a locale picker, a language menu or translation files were added or changed, when a string is suspected of staying in the old language
name: verify-language-switch description: 'Prove a language switch translates what the user sees, keeps what they had already typed, and is still in force after a reload. Use when i18n, a locale picker, a language menu or translation files were added or changed, when a string is suspected of staying in the old language or showing a raw translation key, or when a user reports losing their form or their language choice after switching or refreshing.' license: Apache-2.0 metadata: version: 3.7.0 homepage: https://www.reticle.sh repository: https://github.com/reticlehq/reticle
Clicking the language menu and seeing one word change proves one string was wired up. It does not prove the rest of the page followed, that the field the user was in the middle of filling still holds what they typed, or that the choice outlives a refresh. Those are three separate bugs. Each ships regularly, and glancing at the page after the click catches none of them.
**Reticle** can prove all three in the running app, with no test code. Not installed? `RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init`, then the [`install-and-verify`](https://github.com/reticlehq/reticle/blob/main/skills/install-and-verify/SKILL.md) skill.
reticle_look({ action: "page", sessionId })Write down two or three visible strings in the current language from **different regions** of the page: the heading, a nav label, a button. One string proves one component; two regions prove the switch reached the page.
Then type a value into a form field, so step 3 has something to find:
reticle_look({ action: "page", sessionId, mode: "interactive" })
reticle_act({ sessionId, ref: "<a text field>", action: "fill", args: { value: "reticle-i18n-4471" } })The result must show `valueChanged: true`. Use a value nothing else on the page contains, so step 3 proves **this** input survived rather than a default being refilled.
Get the switch control's ref from the interactive look. A `<select>` takes `action: "select"` with `{ value }`; a button or menu item takes `click`.
reticle_act_and_wait({ sessionId, ref: "<language control>", action: "select", args: { value: "es" }, until: { kind: "allOf", predicates: [
{ kind: "text", contains: "<heading in the new language>", scope: "h1" },
{ kind: "text", contains: "<heading in the old language>", absent: true },
{ kind: "text", contains: "<nav label in the new language>", scope: "nav" },
{ kind: "text", contains: "<the heading's translation key, e.g. home.title>", absent: true },
{ kind: "console", level: "error", absent: true },
]}})Each clause catches a different bug. New text present **and** old text absent proves the switch replaced the string rather than rendering both. The second region proves it was not one component. The translation-key clause catches the string that fell back to its key because the target locale is missing it. The console clause catches the i18n library reporting a missing key as an error. If yours reports at `warn`, add `{ kind: "console", level: "warn", contains: "missing", absent: true }`.
If the app stores the choice on the server, add `{ kind: "net", urlContains: "/api/preferences", ok: true }` with the app's real endpoint, so a switch that only changed the client is not reported as saved.
**If the switch is a full page load** (a `?lang=es` query or an `/es/` prefix that reloads the document), the SDK is torn down mid-wait and this verdict will be `unknown`. Use `reticle_navigate({ sessionId, url: "<the app's origin>/es/<route>" })` and run the text assertions on the session that comes back. Then report step 3 as **lost by design**: a switch that reloads the document discards the user's typed input, and that is a finding, not a pass.
reticle_assert({ sessionId, predicate: { kind: "allOf", predicates: [
{ kind: "element", query: { testid: "<the field>", value: "reticle-i18n-4471" } },
{ kind: "element", query: { role: "textbox", name: "<the field's label in the new language>" } },
]}})Query the field by `testid`, because its accessible name is now translated and the `name` you saw in step 1 will miss. **Keep `value` in the query**: without it the predicate collapses to "the field exists", which passes on a field the switch emptied. The second clause proves the label translated with the rest of the page; a form that keeps its values but leaves its labels in the old language is a partial switch.
If the app keeps form state in a registered store, add `{ kind: "state", path: "<form path>", equals: "reticle-i18n-4471" }`. A field that re-rendered empty while the store kept the value, or the reverse, is the desync only the store read finds.
Use a real reload, not a navigate to the URL you are already on:
reticle_navigate({ sessionId, reload: true })It must answer `confirmed: true` with a `sessionId`. Assert on that session, which is
AI agents can generate code, but still struggle to understand what they build. Reticle brings Jev-style machine-native runtime perception to web & desktop applications.
Repo: reticlehq/reticle
Test-driven development for behaviour a unit test cannot reach, by writing the expectation…
Sweep a whole running web app for what is broken, without writing a script or knowing the…
Find out why something in a running web app does not work, when the console is empty and the…
Check that the UI you actually rendered uses the design system, by reading computed styles in…
Drive and verify an Electron or Tauri desktop app from the inside, including the main-process…
Find out why the tests pass but the app is broken. Catches false greens: a green suite over a…